[
  {
    "path": ".formatter.exs",
    "content": "# Used by \"mix format\" and to export configuration.\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nThis project follows the [Elixir Code of Conduct](https://github.com/elixir-lang/elixir/blob/main/CODE_OF_CONDUCT.md).\n\nWe are committed to providing a friendly, safe, and welcoming environment for\nall contributors. By participating in this project, you agree to abide by the\nstandards outlined in the Elixir Code of Conduct.\n\nIf you experience or witness behavior that violates the Code of Conduct, please\nreport it to: [elixir-lang-conduct@googlegroups.com](mailto:elixir-lang-conduct@googlegroups.com).\n\nThank you for helping us foster a respectful and inclusive community!\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to `gettext`\n\n## Welcome!\n\nWe look forward to your contributions! Here are some examples how you can\ncontribute:\n\n- [Report an issue](https://github.com/elixir-gettext/gettext/issues/new)\n- [Send a pull request](https://github.com/elixir-gettext/gettext/pulls)\n\n## We have a Code of Conduct\n\nPlease note that this project is released with a\n[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this\nproject you agree to abide by its terms.\n\n## Any contributions you make will be under the Apache 2.0 License\n\nWhen you submit code changes, your submissions are understood to be under the\nsame [Apache 2.0](https://github.com/elixir-gettext/gettext/blob/main/LICENSE)\nthat covers the project. By contributing to this project, you agree that your\ncontributions will be licensed under its Apache 2.0 License.\n\n## Write bug reports with detail, background, and sample code\n\nIn your bug report, please provide the following:\n\n- A quick summary and/or background\n- Steps to reproduce\n  - Be specific!\n  - Give sample code if you can.\n- What you expected would happen\n- What actually happens\n- Notes (possibly including why you think this might be happening, or stuff you\n- tried that didn't work)\n\n<!-- TODO: Put in once v1 is released -->\n<!-- Please do not report a bug for a version of `gettext` that is no longer\nsupported (`< 1.0.0`). Please do not report a bug if you are using a version of\nErlang or Elixir that is not supported by the version of `gettext` you are using. -->\n\nPlease post code and output as text\n([using proper markup](https://guides.github.com/features/mastering-markdown/)).\nDo not post screenshots of code or output.\n\n## Workflow for Pull Requests\n\n1. Fork the repository.\n2. Create your branch from `main` if you plan to implement new functionality or\n   change existing code significantly; create your branch from the oldest branch\n   that is affected by the bug if you plan to fix a bug.\n3. Implement your change and add tests for it.\n4. Ensure the test suite passes.\n5. Ensure the code complies with our coding guidelines (see below).\n6. Send that pull request!\n\nPlease make sure you have\n[set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup)\nfor use with Git. Strings such as `silly nick name <root@localhost>` look really\nstupid in the commit history of a project.\n\nWe encourage you to\n[sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits).\n\nPull requests for new features must be based on the `main` branch.\n\nWe are trying to keep backwards compatibility breaks in `gettext` to a\nminimum. Please take this into account when proposing changes.\n\nDue to time constraints, we are not always able to respond as quickly as we\nwould like. Please do not take delays personal and feel free to remind us if you\nfeel that we forgot to respond.\n\n## Coding Guidelines\n\nThis project comes with configuration (located in `.formatter.exs` in the\nrepository) that you can use to (re)format your\nsource code for compliance with this project's coding guidelines:\n\n```bash\n$ mix format\n```\n\nPlease understand that we will not accept a pull request when its changes\nviolate this project's coding guidelines.\n\n## Using `gettext` from a Git checkout\n\nThe following commands can be used to perform the initial checkout of\n`gettext`:\n\n```bash\n$ git clone git@github.com:elixir-gettext/gettext.git\n\n$ cd gettext\n```\n\nInstall `gettext`'s dependencies using [mix](https://hexdocs.pm/mix/Mix.html):\n\n```bash\n$ mix deps.get\n```\n\n## Running `gettext`'s test suite\n\nAfter following the steps shown above, `gettext`'s test suite is run like\nthis:\n\n```bash\n$ mix test\n```\n\n## Generating `gettext` Documentation\n\nTo generate the documentation for the library, run:\n\n```bash\n$ mix docs\n```\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n[![OpenSSF Vulnerability Disclosure](https://img.shields.io/badge/OpenSSF-Vulnerability_Disclosure-green)][openssf-cvd-finders-guide]\n[![GitHub Report](https://img.shields.io/badge/GitHub-Security_Advisories-blue)][github-advisory-new]\n[![Email Report](https://img.shields.io/badge/Email-hi%40andrealeopardi.com-blue)][email]\n\nWe take the security of this software seriously and are committed to ensuring\nthat any vulnerabilities are addressed promptly and effectively.\n\nThis repository follows the OpenSSF\n[Vulnerability Disclosure guide][openssf-cvd-guide].\nYou can learn more about it in the [Finders Guide][openssf-cvd-finders-guide].\n\n## Reporting Security Issues\n\nIf you believe you have found a security vulnerability in this repository,\nplease report it via [GitHub Security Vulnerability Reporting][github-advisory-new]\nor via email to [`hi@andrealeopardi.com`][email] if that is more suitable for you.\n\n**Please do not report vulnerabilities through public channels** such as GitHub\nissues, discussions, or pull requests, to avoid exposing the details of the\nissue before it has been properly addressed.\n\nWe don't implement a bug bounty program or bounty rewards, but will work with\nyou to ensure that your findings get the appropriate handling.\n\nWhen reporting a vulnerability, please include as much detail as possible to\nhelp us triage and resolve the issue efficiently. Information that will be\nspecially helpful includes:\n\n- The type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.)\n- Full paths of source file(s) related to the issue\n- The location of the affected source code (e.g., tag, branch, commit, or direct URL)\n- Any special configuration required to reproduce the issue\n- Step-by-step instructions to reproduce the issue\n- Proof-of-concept or exploit code (if available)\n- The potential impact, including how the issue might be exploited by an attacker\n\nOur vulnerability management team will respond within 3 working days of your\nreport. If the issue is confirmed as a vulnerability, we will open a Security\nAdvisory. This project follows a 90-day disclosure timeline.\n\nIf you have any questions about reporting security issues, please contact our\nvulnerability management team at [`hi@andrealeopardi.com`][email].\n\n[openssf-cvd-guide]: https://github.com/ossf/oss-vulnerability-guide/tree/main\n[openssf-cvd-finders-guide]: https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md\n[github-advisory-new]: /security/advisories/new\n[email]: mailto:hi@andrealeopardi.com\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: monthly\n    open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    name: Test (Elixir ${{ matrix.elixir }} | Erlang/OTP ${{ matrix.erlang }})\n    runs-on: \"${{ matrix.os }}\"\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: ubuntu-22.04\n            erlang: \"27.2\"\n            elixir: \"1.18\"\n            lint: true\n            coverage: true\n\n          - os: ubuntu-22.04\n            erlang: \"24.2\"\n            elixir: \"1.14\"\n    env:\n      MIX_ENV: test\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Install OTP and Elixir\n        uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0\n        with:\n          otp-version: ${{ matrix.erlang }}\n          elixir-version: ${{ matrix.elixir }}\n\n      - name: Install dependencies\n        run: mix deps.get --check-locked\n\n      - name: Check no unused dependencies\n        run: mix deps.unlock --check-unused\n        if: ${{ matrix.lint }}\n\n      - name: Check formatting\n        run: mix format --check-formatted\n        if: ${{ matrix.lint }}\n\n      - name: Compile with --warnings-as-errors\n        run: mix compile --warnings-as-errors\n        if: ${{ matrix.lint }}\n\n      - name: Run tests\n        run: mix test\n        if: ${{ !matrix.coverage }}\n\n      - name: Run tests with code coverage\n        run: mix coveralls.github\n        if: ${{ matrix.coverage }}\n"
  },
  {
    "path": ".github/workflows/publish-to-hex.yml",
    "content": "name: Publish to Hex\n\non:\n  push:\n    tags:\n      - v*\n\npermissions:\n  contents: read\n\njobs:\n  publish:\n    name: Publish to Hex\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout this repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Install Erlang and Elixir\n        uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0\n        with:\n          otp-version: \"27.2\"\n          elixir-version: \"1.18\"\n\n      - name: Fetch dependencies\n        run: mix deps.get\n\n      - name: Publish to Hex\n        run: mix hex.publish --yes\n        env:\n          HEX_API_KEY: ${{ secrets.HEX_API_KEY }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/_build\n/cover\n/deps\n/doc\n/src/gettext_po_parser.erl\n/tmp\nerl_crash.dump\n*.ez\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v1.0.2\n\n  * Only skip manifest removal on Elixir v1.19.3+\n\n## v1.0.1 (retired)\n\n  * Remove unnecessary cleaning of Elixir manifests\n\n## v1.0.0\n\nThis is the first 1.0 release of Gettext, a silly 10 years (and 6 months) after we started working on it. There are *very few changes* from the latest 0.26 release, and none of them are breaking.\n\nHere are the new goodies:\n\n  * Add support for concatenating sigils if all parts are known at compile time (such as `\"Hello \" <> ~s(world)`).\n  * Significantly increase the timeout for `mix gettext.extract` to two minutes.\n  * Add `Gettext.put_locale!/2`.\n\nHappy 10+ years of Elixir translations everyone! 🎉\n\n## Previous versions\n\n[See the CHANGELOG for versions before v1.0](https://github.com/elixir-gettext/gettext/blob/v1.0.0/CHANGELOG.md).\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2015 Plataformatec Copyright 2020 Dashbit\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# Gettext\n\n[![hex.pm badge](https://img.shields.io/badge/Package%20on%20hex.pm-informational)](https://hex.pm/packages/gettext)\n[![Documentation badge](https://img.shields.io/badge/Documentation-ff69b4)][docs-gettext]\n![CI badge](https://github.com/elixir-gettext/gettext/workflows/CI/badge.svg)\n\nGettext is an **internationalization** (i18n) and **localization** (l10n) system commonly used for writing multilingual programs. Gettext is a standard for i18n in different communities, meaning there is a great set of tooling for developers and translators. This project is an implementation of the Gettext system in Elixir.\n\n## Installation\n\nAdd `:gettext` to your list of dependencies in `mix.exs` (use `$ mix hex.info gettext` to find the latest version):\n\n```elixir\ndefp deps do\n  [\n    {:gettext, \">= 0.0.0\"}\n  ]\nend\n```\n\nDocumentation for `Gettext` is [available on Hex][docs-gettext].\n\n## Usage\n\nTo use Gettext, define a **Gettext backend**:\n\n```elixir\ndefmodule MyApp.Gettext do\n  use Gettext.Backend, otp_app: :my_app\nend\n```\n\nand invoke the Gettext API, which consists of the `*gettext` macros that get imported if you `use Gettext`:\n\n```elixir\nuse Gettext, backend: MyApp.Gettext\n\n# Simple message\ngettext(\"Here is one string to translate\")\n\n# Plural message\nnumber_of_apples = 4\nngettext(\"The apple is ripe\", \"The apples are ripe\", number_of_apples)\n\n# Domain-based message\ndgettext(\"errors\", \"Here is an error message to translate\")\n```\n\nMessages in Gettext are stored in Portable Object files (`.po`). Such files must be placed at `priv/gettext/LOCALE/LC_MESSAGES/DOMAIN.po`, where `LOCALE` is the locale and `DOMAIN` is the domain (the default domain is called `default`).\n\nFor example, the messages for `pt_BR` from the first two `*gettext` calls in the snippet above must be placed in the `priv/gettext/pt_BR/LC_MESSAGES/default.po` file with the following contents:\n\n```pot\nmsgid \"Here is one string to translate\"\nmsgstr \"Aqui está um texto para traduzir\"\n\nmsgid \"Here is the string to translate\"\nmsgid_plural \"Here are the strings to translate\"\nmsgstr[0] \"Aqui está o texto para traduzir\"\nmsgstr[1] \"Aqui estão os textos para traduzir\"\n```\n\n`.po` files are text-based and can be edited directly by translators. Some may even use existing tools for managing them, such as [Poedit][poedit] or [poeditor.com][poeditor.com].\n\nFinally, because messages are based on strings, your source code does not lose readability as you still see literal strings, like `gettext(\"here is an example\")`, instead of paths like `translate(\"some.path.convention\")`.\n\nRead the [documentation for the `Gettext` module][docs-gettext-module] for more information on locales, interpolation, pluralization, and other features.\n\n## Workflow\n\nGettext is able to automatically extract messages from your source code, alleviating developers and translators from the repetitive and error-prone work of maintaining message files.\n\nWhen extracted from source, Gettext places messages into `.pot` files, which are template files. You can then merge those templates files into message files for each specific locale your application is being currently translated to.\n\nIn other words, the typical workflow looks like this:\n\n  1. Add `gettext` calls to your source code. No need to touch message files\n     at this point as Gettext will return the given string if no message is\n     available:\n\n     ```elixir\n     gettext(\"Welcome back!\")\n     ```\n\n  2. Once changes to the source are complete, automatically sync all existing entries to `.pot` (template files) in `priv/gettext` by running:\n\n     ```bash\n     mix gettext.extract\n     ```\n\n  3. You can then merge `.pot` files into locale-specific `.po` files:\n\n     ```bash\n     # Merge .pot into all locales\n     mix gettext.merge priv/gettext\n\n     # Merge .pot into one specific locale\n     mix gettext.merge priv/gettext --locale en\n     ```\n\nIt is also possible to both extract and merge messages in one step with `mix gettext.extract --merge`.\n\n## License\n\nCopyright 2015 Plataformatec\nCopyright 2020 Dashbit\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at:\n\n  > <http://www.apache.org/licenses/LICENSE-2.0>\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n\n[docs-gettext]: http://hexdocs.pm/gettext\n[docs-gettext-module]: http://hexdocs.pm/gettext/Gettext.html\n[poedit]: http://poedit.net/\n[poeditor.com]: https://poeditor.com\n"
  },
  {
    "path": "coveralls.json",
    "content": "{\n  \"coverage_options\": {\n    \"treat_no_relevant_lines_as_covered\": true\n  },\n  \"skip_files\": [\n    \"src/gettext_po_parser.erl\",\n    \"lib/mix/tasks/compile.gettext.ex\"\n  ]\n}\n"
  },
  {
    "path": "lib/gettext/application.ex",
    "content": "defmodule Gettext.Application do\n  @moduledoc false\n\n  use Application\n\n  @impl true\n  def start(_type, _args) do\n    children = [Gettext.ExtractorAgent]\n    Supervisor.start_link(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "lib/gettext/backend.ex",
    "content": "defmodule Gettext.Backend do\n  @moduledoc \"\"\"\n  Defines a Gettext backend.\n\n  ## Usage\n\n  A Gettext **backend** must `use` this module.\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n  Using this module generates all the callbacks required by the `Gettext.Backend`\n  behaviour into the module that uses it. For more options and information,\n  see `Gettext`.\n\n  > #### `use Gettext.Backend` Is a Recent Feature {: .info}\n  >\n  > Before version v0.26.0, you could only `use Gettext` to generate a backend.\n  >\n  > Version v0.26.0 changes the way backends work so that now a Gettext backend\n  > must `use Gettext.Backend`, while to use the functions in the backend you\n  > will do `use Gettext, backend: MyApp.Gettext`.\n  \"\"\"\n\n  defmacro __using__(opts) do\n    # TODO: From Elixir v1.13 onwards, use compile_env and remove this if.\n    env_fun = if function_exported?(Module, :attributes_in, 1), do: :compile_env, else: :get_env\n\n    quote do\n      require Logger\n\n      opts = unquote(opts)\n      otp_app = Keyword.fetch!(opts, :otp_app)\n\n      @gettext_opts opts\n                    |> Keyword.merge(Application.unquote(env_fun)(otp_app, __MODULE__, []))\n                    |> Keyword.put_new(:interpolation, Gettext.Interpolation.Default)\n\n      @interpolation Keyword.fetch!(@gettext_opts, :interpolation)\n\n      @before_compile Gettext.Compiler\n\n      def handle_missing_bindings(exception, incomplete) do\n        _ = Logger.error(Exception.message(exception))\n        incomplete\n      end\n\n      defoverridable handle_missing_bindings: 2\n\n      def handle_missing_translation(_locale, domain, _msgctxt, msgid, bindings) do\n        Gettext.Compiler.warn_if_domain_contains_slashes(domain)\n\n        with {:ok, interpolated} <- @interpolation.runtime_interpolate(msgid, bindings),\n             do: {:default, interpolated}\n      end\n\n      def handle_missing_plural_translation(\n            _locale,\n            domain,\n            _msgctxt,\n            msgid,\n            msgid_plural,\n            n,\n            bindings\n          ) do\n        Gettext.Compiler.warn_if_domain_contains_slashes(domain)\n        string = if n == 1, do: msgid, else: msgid_plural\n        bindings = Map.put(bindings, :count, n)\n\n        with {:ok, interpolated} <- @interpolation.runtime_interpolate(string, bindings),\n             do: {:default, interpolated}\n      end\n\n      defoverridable handle_missing_translation: 5, handle_missing_plural_translation: 7\n    end\n  end\n\n  @doc \"\"\"\n  Default handling for missing bindings.\n\n  This function is called when there are missing bindings in a message. It\n  takes a `Gettext.MissingBindingsError` struct and the message with the\n  wrong bindings left as is with the `%{}` syntax.\n\n  For example, if something like this is called:\n\n      gettext(\"Hello %{name}, your favorite color is %{color}\", name: \"Jane\", color: \"blue\")\n\n  and our `it/LC_MESSAGES/default.po` looks like this:\n\n      msgid \"Hello %{name}, your favorite color is %{color}\"\n      msgstr \"Ciao %{name}, il tuo colore preferito è %{colour}\" # (typo)\n\n  then Gettext will call:\n\n      MyApp.Gettext.handle_missing_bindings(exception, \"Ciao Jane, il tuo colore preferito è %{colour}\")\n\n  where `exception` is a struct that looks like this:\n\n      %Gettext.MissingBindingsError{\n        backend: MyApp.Gettext,\n        domain: \"default\",\n        locale: \"it\",\n        msgid: \"Ciao %{name}, il tuo colore preferito è %{colour}\",\n        bindings: [:colour],\n      }\n\n  The return value of the `c:handle_missing_bindings/2` callback is used as the\n  translated string that the message macros and functions return.\n\n  The default implementation for this function uses `Logger.error/1` to warn\n  about the missing binding and returns the translated message with the\n  incomplete bindings.\n\n  This function can be overridden. For example, to raise when there are missing\n  bindings:\n\n      def handle_missing_bindings(exception, _incomplete) do\n        raise exception\n      end\n\n  \"\"\"\n  @callback handle_missing_bindings(Gettext.MissingBindingsError.t(), binary) ::\n              binary | no_return\n\n  @doc \"\"\"\n  Default handling for messages with a missing message.\n\n  When a Gettext function/macro is called with a string to translate\n  into a locale but that locale doesn't provide a message for that\n  string, this callback is invoked. `msgid` is the string that Gettext\n  tried to translate.\n\n  This function should return `{:ok, translated}` if a message can be\n  fetched or constructed for the given string. If you cannot find a\n  message, it should return `{:default, translated}`, where the\n  translated string defaults to the interpolated msgid. You can, however,\n  customize the default to, for example, pick the message from the\n  default locale. The important is to return `:default` instead of `:ok`\n  whenever the result does not quite match the requested locale.\n\n  Earlier versions of this library provided a callback without msgctxt.\n  Users implementing that callback will still get the same results,\n  but they are encouraged to switch to the new 5-argument version.\n  \"\"\"\n  @callback handle_missing_translation(\n              Gettext.locale(),\n              domain :: String.t(),\n              msgctxt :: String.t(),\n              msgid :: String.t(),\n              bindings :: map()\n            ) ::\n              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}\n\n  @doc \"\"\"\n  Default handling for plural messages with a missing message.\n\n  Same as `c:handle_missing_translation/5`, but for plural messages.\n  In this case, `n` is the number used for pluralizing the translated string.\n\n  Earlier versions of this library provided a callback without msgctxt.\n  Users implementing that callback will still get the same results,\n  but they are encouraged to switch to the new 7-argument version.\n  \"\"\"\n  @callback handle_missing_plural_translation(\n              Gettext.locale(),\n              domain :: String.t(),\n              msgctxt :: String.t(),\n              msgid :: String.t(),\n              msgid_plural :: String.t(),\n              n :: non_neg_integer(),\n              bindings :: map()\n            ) ::\n              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}\n\n  @doc \"\"\"\n  Translates a message.\n\n  See `Gettext.gettext/3` for more information.\n  \"\"\"\n  @doc since: \"0.26.0\"\n  @callback lgettext(\n              Gettext.locale(),\n              domain :: String.t(),\n              msgctxt :: String.t() | nil,\n              msgid :: String.t(),\n              bindings :: map()\n            ) ::\n              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}\n\n  @doc \"\"\"\n  Translates a plural message.\n\n  See `Gettext.ngettext/5` for more information.\n  \"\"\"\n  @doc since: \"0.26.0\"\n  @callback lngettext(\n              Gettext.locale(),\n              domain :: String.t(),\n              msgctxt :: String.t() | nil,\n              msgid :: String.t(),\n              msgid_plural :: String.t(),\n              n :: non_neg_integer(),\n              bindings :: map()\n            ) ::\n              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}\nend\n"
  },
  {
    "path": "lib/gettext/compiler.ex",
    "content": "defmodule Gettext.Compiler do\n  @moduledoc false\n\n  require Logger\n\n  alias Expo.Message\n  alias Expo.Messages\n  alias Expo.PO\n  alias Gettext.Plural\n\n  @default_priv \"priv/gettext\"\n  @default_domain \"default\"\n  @po_wildcard \"*/LC_MESSAGES/*.po\"\n\n  @doc false\n  def __hash__(priv) do\n    hash(po_files_in_priv(priv))\n  end\n\n  defp hash(all_po_files) do\n    all_po_files |> Enum.sort() |> :erlang.md5()\n  end\n\n  @doc false\n  defmacro __before_compile__(env) do\n    opts = Module.get_attribute(env.module, :gettext_opts)\n    otp_app = Keyword.fetch!(opts, :otp_app)\n    priv = Keyword.get(opts, :priv, @default_priv)\n    all_po_files = po_files_in_priv(priv)\n    hash_po_files = hash(all_po_files)\n    known_po_files = known_po_files(all_po_files, opts)\n    known_locales = Enum.map(known_po_files, & &1[:locale]) |> Enum.uniq()\n\n    default_locale =\n      opts[:default_locale] || quote(do: Application.fetch_env!(:gettext, :default_locale))\n\n    default_domain = opts[:default_domain] || @default_domain\n\n    interpolation = opts[:interpolation] || Gettext.Interpolation.Default\n\n    quote do\n      @behaviour Gettext.Backend\n\n      unquote(external_resources(known_po_files))\n\n      @doc false\n      def __mix_recompile__? do\n        unquote(hash_po_files) != Gettext.Compiler.__hash__(unquote(priv))\n      end\n\n      # Info about the Gettext backend.\n      @doc false\n      def __gettext__(:priv), do: unquote(priv)\n      def __gettext__(:otp_app), do: unquote(otp_app)\n      def __gettext__(:known_locales), do: unquote(known_locales)\n      def __gettext__(:default_locale), do: unquote(default_locale)\n      def __gettext__(:default_domain), do: unquote(default_domain)\n      def __gettext__(:interpolation), do: unquote(interpolation)\n\n      if Gettext.Extractor.extracting?() do\n        Gettext.ExtractorAgent.add_backend(__MODULE__)\n      end\n\n      # These are the two functions we generate inside the backend.\n\n      @impl Gettext.Backend\n      def lgettext(locale, domain, msgctxt \\\\ nil, msgid, bindings)\n\n      @impl Gettext.Backend\n      def lngettext(locale, domain, msgctxt \\\\ nil, msgid, msgid_plural, n, bindings)\n\n      unquote(compile_po_files(env, known_po_files, opts))\n\n      # Catch-all clauses.\n      def lgettext(locale, domain, msgctxt, msgid, bindings),\n        do: handle_missing_translation(locale, domain, msgctxt, msgid, bindings)\n\n      def lngettext(locale, domain, msgctxt, msgid, msgid_plural, n, bindings),\n        do:\n          handle_missing_plural_translation(\n            locale,\n            domain,\n            msgctxt,\n            msgid,\n            msgid_plural,\n            n,\n            bindings\n          )\n    end\n  end\n\n  defp external_resources(known_po_files) do\n    Enum.map(known_po_files, fn po_file ->\n      quote do\n        @external_resource unquote(po_file.path)\n      end\n    end)\n  end\n\n  ## BEGIN of deprecated code\n  ## TODO: Remove this block once \"use Gettext\" is removed\n\n  defmacro generate_macros(_env) do\n    quote unquote: false do\n      defmacro dpgettext_noop(domain, msgctxt, msgid) do\n        domain = Gettext.Compiler.expand_to_binary(domain, \"domain\", __MODULE__, __CALLER__)\n        msgid = Gettext.Compiler.expand_to_binary(msgid, \"msgid\", __MODULE__, __CALLER__)\n        msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, \"msgctxt\", __MODULE__, __CALLER__)\n\n        if Gettext.Extractor.extracting?() do\n          Gettext.Extractor.extract(\n            __CALLER__,\n            __MODULE__,\n            domain,\n            msgctxt,\n            msgid,\n            Gettext.Compiler.get_and_flush_extracted_comments()\n          )\n        end\n\n        msgid\n      end\n\n      defmacro dgettext_noop(domain, msgid) do\n        quote do\n          unquote(__MODULE__).dpgettext_noop(unquote(domain), nil, unquote(msgid))\n        end\n      end\n\n      defmacro gettext_noop(msgid) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpgettext_noop(unquote(domain), nil, unquote(msgid))\n        end\n      end\n\n      defmacro pgettext_noop(msgid, context) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpgettext_noop(unquote(domain), unquote(context), unquote(msgid))\n        end\n      end\n\n      defmacro dpngettext_noop(domain, msgctxt, msgid, msgid_plural) do\n        domain = Gettext.Compiler.expand_to_binary(domain, \"domain\", __MODULE__, __CALLER__)\n        msgid = Gettext.Compiler.expand_to_binary(msgid, \"msgid\", __MODULE__, __CALLER__)\n        msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, \"msgctxt\", __MODULE__, __CALLER__)\n\n        msgid_plural =\n          Gettext.Compiler.expand_to_binary(msgid_plural, \"msgid_plural\", __MODULE__, __CALLER__)\n\n        if Gettext.Extractor.extracting?() do\n          Gettext.Extractor.extract(\n            __CALLER__,\n            __MODULE__,\n            domain,\n            msgctxt,\n            {msgid, msgid_plural},\n            Gettext.Compiler.get_and_flush_extracted_comments()\n          )\n        end\n\n        {msgid, msgid_plural}\n      end\n\n      defmacro dngettext_noop(domain, msgid, msgid_plural) do\n        quote do\n          unquote(__MODULE__).dpngettext_noop(\n            unquote(domain),\n            nil,\n            unquote(msgid),\n            unquote(msgid_plural)\n          )\n        end\n      end\n\n      defmacro pngettext_noop(msgctxt, msgid, msgid_plural) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpngettext_noop(\n            unquote(domain),\n            unquote(msgctxt),\n            unquote(msgid),\n            unquote(msgid_plural)\n          )\n        end\n      end\n\n      defmacro ngettext_noop(msgid, msgid_plural) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpngettext_noop(\n            unquote(domain),\n            nil,\n            unquote(msgid),\n            unquote(msgid_plural)\n          )\n        end\n      end\n\n      defmacro dpgettext(domain, msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n        quote do\n          msgid =\n            unquote(__MODULE__).dpgettext_noop(unquote(domain), unquote(msgctxt), unquote(msgid))\n\n          Gettext.dpgettext(\n            unquote(__MODULE__),\n            unquote(domain),\n            unquote(msgctxt),\n            msgid,\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro dgettext(domain, msgid, bindings \\\\ Macro.escape(%{})) do\n        quote do\n          unquote(__MODULE__).dpgettext(unquote(domain), nil, unquote(msgid), unquote(bindings))\n        end\n      end\n\n      defmacro pgettext(msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpgettext(\n            unquote(domain),\n            unquote(msgctxt),\n            unquote(msgid),\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro gettext(msgid, bindings \\\\ Macro.escape(%{})) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpgettext(unquote(domain), nil, unquote(msgid), unquote(bindings))\n        end\n      end\n\n      defmacro dpngettext(domain, msgctxt, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n        quote do\n          {msgid, msgid_plural} =\n            unquote(__MODULE__).dpngettext_noop(\n              unquote(domain),\n              unquote(msgctxt),\n              unquote(msgid),\n              unquote(msgid_plural)\n            )\n\n          Gettext.dpngettext(\n            unquote(__MODULE__),\n            unquote(domain),\n            unquote(msgctxt),\n            msgid,\n            msgid_plural,\n            unquote(n),\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro dngettext(domain, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n        quote do\n          unquote(__MODULE__).dpngettext(\n            unquote(domain),\n            nil,\n            unquote(msgid),\n            unquote(msgid_plural),\n            unquote(n),\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro ngettext(msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpngettext(\n            unquote(domain),\n            nil,\n            unquote(msgid),\n            unquote(msgid_plural),\n            unquote(n),\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro pngettext(msgctxt, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n        domain = __gettext__(:default_domain)\n\n        quote do\n          unquote(__MODULE__).dpngettext(\n            unquote(domain),\n            unquote(msgctxt),\n            unquote(msgid),\n            unquote(msgid_plural),\n            unquote(n),\n            unquote(bindings)\n          )\n        end\n      end\n\n      defmacro gettext_comment(comment) do\n        comment = Gettext.Compiler.expand_to_binary(comment, \"comment\", __MODULE__, __CALLER__)\n        Gettext.Compiler.append_extracted_comment(comment)\n        :ok\n      end\n    end\n  end\n\n  @doc false\n  # TODO: remove me\n  def expand_to_binary(term, what, gettext_module, env)\n      when what in ~w(domain msgctxt msgid msgid_plural comment) do\n    raiser = fn term ->\n      raise ArgumentError, \"\"\"\n      Gettext macros expect message keys (msgid and msgid_plural),\n      domains, and comments to expand to strings at compile-time, but the given #{what}\n      doesn't. This is what the macro received:\n\n      #{inspect(term)}\n\n      Dynamic messages should be avoided as they limit Gettext's\n      ability to extract messages from your source code. If you are\n      sure you need dynamic lookup, you can use the functions in the Gettext\n      module:\n\n          string = \"hello world\"\n          Gettext.gettext(#{inspect(gettext_module)}, string)\n      \"\"\"\n    end\n\n    # We support nil too in order to fall back to a nil context and always use the *p\n    # variants of the Gettext macros.\n    case Macro.expand(term, env) do\n      term when is_binary(term) or is_nil(term) ->\n        term\n\n      {:<<>>, _, pieces} = term ->\n        if Enum.all?(pieces, &is_binary/1), do: Enum.join(pieces), else: raiser.(term)\n\n      other ->\n        raiser.(other)\n    end\n  end\n\n  @doc false\n  def append_extracted_comment(comment) do\n    existing = Process.get(:gettext_comments, [])\n    Process.put(:gettext_comments, [\" \" <> comment | existing])\n    :ok\n  end\n\n  @doc false\n  def get_and_flush_extracted_comments() do\n    Enum.reverse(Process.delete(:gettext_comments) || [])\n  end\n\n  ## END of deprecated block\n\n  @doc \"\"\"\n  Logs a warning via `Logger.error/1` if `domain` contains slashes.\n\n  This function is called by `lgettext` and `lngettext`. It could make sense to\n  make this function raise an error since slashes in domains are not supported,\n  but we decided not to do so and to only emit a warning since the expected\n  behaviour for Gettext functions/macros when the domain or message is not\n  known is to return the original string (msgid) and raising here would break\n  that contract.\n  \"\"\"\n  @spec warn_if_domain_contains_slashes(binary) :: :ok\n  def warn_if_domain_contains_slashes(domain) do\n    if String.contains?(domain, \"/\") do\n      _ = Logger.error(fn -> [\"Slashes in domains are not supported: \", inspect(domain)] end)\n    end\n\n    :ok\n  end\n\n  # Compiles all the `.po` files in the given directory (`dir`) into `lgettext/4`\n  # and `lngettext/6` function clauses.\n  defp compile_po_files(env, known_po_files, opts) do\n    plural_mod =\n      Keyword.get(opts, :plural_forms) ||\n        Application.get_env(:gettext, :plural_forms, Gettext.Plural)\n\n    opts =\n      if opts[:one_module_per_locale] do\n        IO.warn(\n          \":one_module_per_locale is deprecated, please use split_module_by: [:locale] instead\"\n        )\n\n        Keyword.put_new(opts, :split_module_by, [:locale])\n      else\n        opts\n      end\n\n    case List.wrap(opts[:split_module_by]) do\n      [] ->\n        Enum.map(\n          known_po_files,\n          &compile_unified_po_file(env, &1, plural_mod, opts[:interpolation])\n        )\n\n      split ->\n        grouped = Enum.group_by(known_po_files, &split_module_name(env, &1, split))\n\n        case Keyword.get(opts, :split_module_compilation, :parallel) do\n          :serial ->\n            Enum.map(grouped, fn {module, files} ->\n              compile_split_po_files(env, module, files, plural_mod, opts[:interpolation])\n            end)\n\n          :parallel ->\n            grouped\n            |> Enum.map(fn {module, files} ->\n              Kernel.ParallelCompiler.async(fn ->\n                compile_split_po_files(env, module, files, plural_mod, opts[:interpolation])\n              end)\n            end)\n            |> Enum.map(fn task ->\n              Task.await(task, :infinity)\n            end)\n        end\n    end\n  end\n\n  defp split_module_name(env, po_file, split) do\n    String.to_atom(\n      \"#{env.module}.T\" <>\n        if(:locale in split, do: \"_\" <> po_file.locale, else: \"\") <>\n        if(:domain in split, do: \"_\" <> po_file.domain, else: \"\")\n    )\n  end\n\n  defp compile_unified_po_file(env, po_file, plural_mod, interpolation_module) do\n    {locale, domain, singular_fun, plural_fun, quoted} =\n      compile_po_file(:defp, po_file, env, plural_mod, interpolation_module)\n\n    quote do\n      unquote(quoted)\n\n      def lgettext(unquote(locale), unquote(domain), msgctxt, msgid, bindings) do\n        unquote(singular_fun)(msgctxt, msgid, bindings)\n      end\n\n      def lngettext(unquote(locale), unquote(domain), msgctxt, msgid, msgid_plural, n, bindings) do\n        unquote(plural_fun)(msgctxt, msgid, msgid_plural, n, bindings)\n      end\n    end\n  end\n\n  defp compile_split_po_files(env, module, files, plural_mod, interpolation_module) do\n    {current, split} =\n      Enum.reduce(\n        files,\n        {[], []},\n        &compile_split_po_file(env, module, plural_mod, &1, interpolation_module, &2)\n      )\n\n    create_split_module(env, module, split)\n    current\n  end\n\n  defp compile_split_po_file(env, module, plural_mod, po_file, interpolation_module, {acc1, acc2}) do\n    {locale, domain, singular_fun, plural_fun, split_module_quoted} =\n      compile_po_file(:def, po_file, env, plural_mod, interpolation_module)\n\n    current_module_quoted =\n      quote do\n        def lgettext(unquote(locale), unquote(domain), msgctxt, msgid, bindings) do\n          unquote(module).unquote(singular_fun)(msgctxt, msgid, bindings)\n        end\n\n        def lngettext(unquote(locale), unquote(domain), msgctxt, msgid, msgid_plural, n, bindings) do\n          unquote(module).unquote(plural_fun)(msgctxt, msgid, msgid_plural, n, bindings)\n        end\n      end\n\n    {[current_module_quoted | acc1], [split_module_quoted | acc2]}\n  end\n\n  defp create_split_module(env, module, messages) do\n    exprs = [quote(do: @moduledoc(false)) | messages]\n    Module.create(module, block(exprs), env)\n    :ok\n  end\n\n  # Compiles a .po file into a list of lgettext/5 (for messages) and\n  # lngettext/7 (for plural messages) clauses.\n  defp compile_po_file(kind, po_file, env, plural_mod, interpolation_module) do\n    %{locale: locale, domain: domain, path: path} = po_file\n\n    %Messages{messages: messages, file: file} =\n      messages_struct = PO.parse_file!(path, strip_meta: true)\n\n    plural_forms_fun = :\"#{locale}_#{domain}_plural\"\n\n    plural_forms = compile_plural_forms(locale, messages_struct, plural_mod, plural_forms_fun)\n    nplurals = nplurals(locale, messages_struct, plural_mod)\n\n    singular_fun = :\"#{locale}_#{domain}_lgettext\"\n    plural_fun = :\"#{locale}_#{domain}_lngettext\"\n\n    messages = Enum.filter(messages, &match?(%{obsolete: false}, &1))\n\n    mapper =\n      &compile_message(\n        kind,\n        locale,\n        &1,\n        singular_fun,\n        plural_fun,\n        file,\n        {plural_forms_fun, nplurals},\n        interpolation_module\n      )\n\n    messages = block(Enum.map(messages, mapper))\n\n    quoted =\n      quote do\n        unquote(plural_forms)\n\n        unquote(messages)\n\n        Kernel.unquote(kind)(unquote(singular_fun)(msgctxt, msgid, bindings)) do\n          unquote(env.module).handle_missing_translation(\n            unquote(locale),\n            unquote(domain),\n            msgctxt,\n            msgid,\n            bindings\n          )\n        end\n\n        Kernel.unquote(kind)(unquote(plural_fun)(msgctxt, msgid, msgid_plural, n, bindings)) do\n          unquote(env.module).handle_missing_plural_translation(\n            unquote(locale),\n            unquote(domain),\n            msgctxt,\n            msgid,\n            msgid_plural,\n            n,\n            bindings\n          )\n        end\n      end\n\n    {locale, domain, singular_fun, plural_fun, quoted}\n  end\n\n  defp nplurals(locale, messages_struct, plural_mod) do\n    plural_mod.nplurals(Plural.plural_info(locale, messages_struct, plural_mod))\n  end\n\n  defp compile_plural_forms(locale, messages_struct, plural_mod, plural_fun) do\n    quote do\n      defp unquote(plural_fun)(n) do\n        unquote(plural_mod).plural(\n          unquote(Macro.escape(Plural.plural_info(locale, messages_struct, plural_mod))),\n          n\n        )\n      end\n    end\n  end\n\n  defp locale_and_domain_from_path(path) do\n    [file, \"LC_MESSAGES\", locale | _rest] = path |> Path.split() |> Enum.reverse()\n    domain = Path.rootname(file, \".po\")\n    {locale, domain}\n  end\n\n  defp compile_message(\n         kind,\n         _locale,\n         %Message.Singular{} = message,\n         singular_fun,\n         _plural_fun,\n         _file,\n         _pluralization,\n         interpolation_module\n       ) do\n    msgid = IO.iodata_to_binary(message.msgid)\n    msgstr = IO.iodata_to_binary(message.msgstr)\n    msgctxt = message.msgctxt && IO.iodata_to_binary(message.msgctxt)\n\n    case msgstr do\n      # Only actually generate this function clause if the msgstr is not empty.\n      # If it is empty, it will trigger the missing message case.\n      \"\" ->\n        nil\n\n      _ ->\n        quote do\n          Kernel.unquote(kind)(\n            unquote(singular_fun)(unquote(msgctxt), unquote(msgid), bindings)\n          ) do\n            require unquote(interpolation_module)\n\n            unquote(interpolation_module).compile_interpolate(\n              :translation,\n              unquote(msgstr),\n              bindings\n            )\n          end\n        end\n    end\n  end\n\n  defp compile_message(\n         kind,\n         locale,\n         %Message.Plural{} = message,\n         singular_fun,\n         plural_fun,\n         file,\n         {plural_forms_fun, nplurals},\n         interpolation_module\n       ) do\n    warn_if_missing_plural_forms(locale, nplurals, message, file)\n\n    msgid = IO.iodata_to_binary(message.msgid)\n    msgid_plural = IO.iodata_to_binary(message.msgid_plural)\n    msgstr = Enum.map(message.msgstr, fn {form, str} -> {form, IO.iodata_to_binary(str)} end)\n    msgctxt = message.msgctxt && IO.iodata_to_binary(message.msgctxt)\n\n    # If any of the msgstrs is empty, then we skip the generation of this\n    # function clause. The reason we do this is the same as for the\n    # `%Message.Singular{}` clause.\n    unless Enum.any?(msgstr, &match?({_form, \"\"}, &1)) do\n      # We use flat_map here because clauses can only be defined in blocks,\n      # so when quoted they are a list.\n      clauses =\n        Enum.flat_map(msgstr, fn {form, str} ->\n          quote do\n            unquote(form) ->\n              require unquote(interpolation_module)\n\n              unquote(interpolation_module).compile_interpolate(\n                :plural_translation,\n                unquote(str),\n                var!(bindings)\n              )\n          end\n        end)\n\n      error_clause =\n        quote do\n          form ->\n            raise Gettext.PluralFormError,\n              form: form,\n              locale: unquote(locale),\n              file: unquote(file),\n              line: unquote(Message.source_line_number(message, :msgid))\n        end\n\n      singular_fun_impl =\n        msgstr\n        |> Enum.find(&match?({0, _msgstr}, &1))\n        |> case do\n          {0, \"\"} ->\n            nil\n\n          {0, msgstr} ->\n            quote do\n              Kernel.unquote(kind)(\n                unquote(singular_fun)(unquote(msgctxt), unquote(msgid), bindings)\n              ) do\n                require unquote(interpolation_module)\n\n                unquote(interpolation_module).compile_interpolate(\n                  :translation,\n                  unquote(msgstr),\n                  bindings\n                )\n              end\n            end\n\n          nil ->\n            nil\n        end\n\n      plural_fun_impl =\n        quote generated: true do\n          Kernel.unquote(kind)(\n            unquote(plural_fun)(\n              unquote(msgctxt),\n              unquote(msgid),\n              unquote(msgid_plural),\n              n,\n              bindings\n            )\n          ) do\n            plural_form = unquote(plural_forms_fun)(n)\n\n            var!(bindings) = Map.put(bindings, :count, n)\n\n            case plural_form, do: unquote(clauses ++ error_clause)\n          end\n        end\n\n      quote do\n        unquote(singular_fun_impl)\n        unquote(plural_fun_impl)\n      end\n    end\n  end\n\n  defp warn_if_missing_plural_forms(locale, nplurals, message, file) do\n    Enum.each(0..(nplurals - 1), fn form ->\n      unless Map.has_key?(message.msgstr, form) do\n        _ =\n          Logger.error([\n            \"#{file}:#{Message.source_line_number(message, :msgid)}: message is missing plural form \",\n            Integer.to_string(form),\n            \" which is required by the locale \",\n            inspect(locale)\n          ])\n      end\n    end)\n  end\n\n  defp block(contents) when is_list(contents) do\n    {:__block__, [], contents}\n  end\n\n  defp po_files_in_priv(priv) do\n    priv\n    |> Path.join(@po_wildcard)\n    |> Path.wildcard()\n  end\n\n  # Returns the known the PO files in `messages_dir` with their locale and domain\n  # If allowed_locales is configured, it removes all the PO files that do not belong\n  # to those locales\n  defp known_po_files(all_po_files, opts) do\n    all_po_files\n    |> Enum.map(fn path ->\n      {locale, domain} = locale_and_domain_from_path(path)\n      %{locale: locale, path: path, domain: domain}\n    end)\n    |> maybe_restrict_locales(opts[:allowed_locales])\n  end\n\n  defp maybe_restrict_locales(po_files, nil) do\n    po_files\n  end\n\n  defp maybe_restrict_locales(po_files, allowed_locales) when is_list(allowed_locales) do\n    allowed_locales = MapSet.new(Enum.map(allowed_locales, &to_string/1))\n    Enum.filter(po_files, &MapSet.member?(allowed_locales, &1[:locale]))\n  end\nend\n"
  },
  {
    "path": "lib/gettext/error.ex",
    "content": "defmodule Gettext.Error do\n  @moduledoc \"\"\"\n  A generic error raised for a variety of possible Gettext-related reasons.\n  \"\"\"\n\n  @typedoc since: \"0.22.0\"\n  @type t() :: %__MODULE__{}\n\n  defexception [:message]\nend\n"
  },
  {
    "path": "lib/gettext/extractor.ex",
    "content": "defmodule Gettext.Extractor do\n  @moduledoc false\n\n  # This module is responsible for extracting messages (it's called from the\n  # *gettext macros) and dumping those messages to POT files, merging with\n  # existing POT files if necessary.\n  #\n  # ## Ordering\n  #\n  # Ordering is mostly taken care of in merge_template/2, where we go over the\n  # messages in an existing POT file and merge them if necessary (thus\n  # keeping the order from the original file), then adding the messages from\n  # the new in-memory POT (sorted by name).\n\n  alias Gettext.Error\n  alias Gettext.ExtractorAgent\n  alias Gettext.Merger\n  alias Expo.PO\n  alias Expo.Message\n  alias Expo.Messages\n\n  @extracted_messages_flag \"elixir-autogen\"\n\n  @new_pot_comment String.split(\n                     \"\"\"\n                     # This file is a PO Template file.\n                     #\n                     # \"msgid\"s here are often extracted from source code.\n                     # Add new messages manually only if they're dynamic\n                     # messages that can't be statically extracted.\n                     #\n                     # Run \"mix gettext.extract\" to bring this file up to\n                     # date. Leave \"msgstr\"s empty as changing them here has no\n                     # effect: edit them in PO (.po) files instead.\n                     \"\"\",\n                     \"\\n\"\n                   )\n\n  @doc \"\"\"\n  Enables message extraction.\n  \"\"\"\n  @spec enable() :: :ok\n  def enable() do\n    ExtractorAgent.enable()\n  end\n\n  @doc \"\"\"\n  Disables extraction.\n  \"\"\"\n  @spec disable() :: :ok\n  def disable() do\n    ExtractorAgent.disable()\n  end\n\n  @doc \"\"\"\n  Tells whether messages are being extracted.\n  \"\"\"\n  @spec extracting?() :: boolean\n  def extracting?() do\n    # Because the extractor agent may not be enabled during compilation\n    # time (as it requires the optional Gettext compiler), we need to\n    # check if the agent is up and running before querying it.\n    Process.whereis(ExtractorAgent) && ExtractorAgent.extracting?()\n  end\n\n  @doc \"\"\"\n  Extracts a message by temporarily storing it in an agent.\n\n  Note that this function doesn't perform any operation on the filesystem.\n  \"\"\"\n  @spec extract(\n          Macro.Env.t(),\n          backend :: module,\n          domain :: binary | :default,\n          msgctxt :: binary,\n          id :: binary | {binary, binary},\n          extracted_comments :: [binary]\n        ) :: :ok\n  def extract(%Macro.Env{} = caller, backend, domain, msgctxt, id, extracted_comments) do\n    format_flag = backend.__gettext__(:interpolation).message_format()\n\n    domain =\n      case domain do\n        :default -> backend.__gettext__(:default_domain)\n        string when is_binary(string) -> string\n      end\n\n    message =\n      create_message_struct(\n        id,\n        msgctxt,\n        caller.file,\n        caller.line,\n        extracted_comments,\n        format_flag\n      )\n\n    ExtractorAgent.add_message(backend, domain, message)\n  end\n\n  @doc \"\"\"\n  Returns a list of POT files based on the results of the extraction.\n\n  Returns a list of paths and their contents to be written to disk. Existing POT\n  files are either purged from obsolete messages (in case no extracted\n  message ends up in that file) or merged with the extracted messages;\n  new POT files are returned for extracted messages that belong to a POT\n  file that doesn't exist yet.\n\n  This is a stateful operation. Once pot_files are generated, their information\n  is permanently removed from the extractor.\n  \"\"\"\n  @spec pot_files(atom, Keyword.t()) :: [{path :: String.t(), contents :: iodata}]\n  def pot_files(app, gettext_config) do\n    backends = ExtractorAgent.pop_backends(app)\n    warn_on_conflicting_backends(backends)\n    existing_pot_files = pot_files_for_backends(backends)\n\n    backends\n    |> ExtractorAgent.pop_message()\n    |> create_po_structs_from_extracted_messages()\n    |> merge_pot_files(existing_pot_files, gettext_config)\n  end\n\n  defp warn_on_conflicting_backends(backends) do\n    Enum.reduce(backends, %{}, fn backend, acc ->\n      priv = backend.__gettext__(:priv)\n\n      case acc do\n        %{^priv => other_backend} ->\n          IO.warn(\n            \"the Gettext backend #{inspect(backend)} has the same :priv directory as \" <>\n              \"#{inspect(other_backend)}, which means they will override each other. \" <>\n              \"Please set the :priv option to different directories or use Gettext \" <>\n              \"inside each backend\"\n          )\n\n          acc\n\n        %{} ->\n          Map.put(acc, priv, backend)\n      end\n    end)\n  end\n\n  # Returns all the .pot files for each of the given `backends`.\n  defp pot_files_for_backends(backends) do\n    Enum.flat_map(backends, fn backend ->\n      backend.__gettext__(:priv)\n      |> Path.join(\"**/*.pot\")\n      |> Path.wildcard()\n    end)\n  end\n\n  # This returns a list of {absolute_path, %Gettext.PO{}} tuples.\n  # `all_messages` looks like this:\n  #\n  #     %{MyBackend => %{\"a_domain\" => %{\"a message id\" => a_message}}}\n  #\n  defp create_po_structs_from_extracted_messages(all_messages) do\n    for {backend, domains} <- all_messages,\n        {domain, messages} <- domains do\n      messages = Map.values(messages)\n      {pot_path(backend, domain), po_struct_from_messages(messages)}\n    end\n  end\n\n  defp pot_path(backend, domain) do\n    Path.join(backend.__gettext__(:priv), \"#{domain}.pot\")\n  end\n\n  defp po_struct_from_messages(messages) do\n    # Sort all the messages and the references of each message in order\n    # to make as few changes as possible to the PO(T) files.\n    messages =\n      messages\n      |> Enum.sort_by(&Message.key/1)\n      |> Enum.map(&sort_references/1)\n\n    %Messages{messages: messages, top_comments: @new_pot_comment, headers: [\"\"]}\n  end\n\n  defp sort_references(message) do\n    update_in(message.references, &Enum.sort/1)\n  end\n\n  defp create_message_struct(\n         {msgid, msgid_plural},\n         msgctxt,\n         file,\n         line,\n         extracted_comments,\n         format_flag\n       ) do\n    %Message.Plural{\n      msgid: [msgid],\n      msgctxt: if(msgctxt != nil, do: [msgctxt], else: nil),\n      msgid_plural: [msgid_plural],\n      msgstr: %{0 => [\"\"], 1 => [\"\"]},\n      flags: [[@extracted_messages_flag, format_flag]],\n      references: [[{Path.relative_to_cwd(file), line}]],\n      extracted_comments: extracted_comments\n    }\n  end\n\n  defp create_message_struct(msgid, msgctxt, file, line, extracted_comments, format_flag) do\n    %Message.Singular{\n      msgid: [msgid],\n      msgctxt: if(msgctxt != nil, do: [msgctxt], else: nil),\n      msgstr: [\"\"],\n      flags: [[@extracted_messages_flag, format_flag]],\n      references: [[{Path.relative_to_cwd(file), line}]],\n      extracted_comments: extracted_comments\n    }\n  end\n\n  # Made public for testing.\n  @doc false\n  def merge_pot_files(po_structs, pot_files, gettext_config) do\n    # pot_files is a list of paths to existing .pot files while po_structs is a\n    # list of {path, struct} for new %Gettext.PO{} structs that we have\n    # extracted. If we turn pot_files into a list of {path, whatever} tuples,\n    # then we can take advantage of Map.merge/3 to find files that we have to\n    # update, delete, or add.\n    pot_files = Map.new(pot_files, &{&1, :existing})\n\n    po_structs =\n      Map.new(po_structs, fn {path, struct} ->\n        {path, Merger.prune_references(struct, gettext_config)}\n      end)\n\n    # After Map.merge/3, we have something like:\n    #   %{path => {:merged, :unchanged | %Messages{}}, path => %Messages{}, path => :existing}\n    # and after mapping tag_files/1 over that we have something like:\n    #   %{path => {:merged, :unchanged | %Messages{}}, path => {:unmerged, :unchanged | %Messages{}}, path => {:new, %Messages{}}}\n    Map.merge(pot_files, po_structs, &merge_existing_and_extracted(&1, &2, &3, gettext_config))\n    |> Enum.map(&tag_files(&1, gettext_config))\n    |> Enum.map(&dump_tagged_file/1)\n  end\n\n  # This function is called by merge_pot_files/2 as the function passed to\n  # Map.merge/3 (so when we have both an :existing file and a new extracted\n  # in-memory PO struct both located at \"path\").\n  defp merge_existing_and_extracted(path, :existing, extracted, gettext_config) do\n    {:merged, merge_or_unchanged(path, extracted, gettext_config)}\n  end\n\n  # Returns :unchanged if merging `existing_path` with `new_po` changes nothing,\n  # otherwise a %Gettext.PO{} struct with the changed contents.\n  defp merge_or_unchanged(existing_path, new_po, gettext_config) do\n    {existing_contents, existing_po} = read_contents_and_parse(existing_path)\n    merged_po = merge_template(existing_po, new_po, gettext_config)\n\n    if IO.iodata_to_binary(PO.compose(merged_po)) == existing_contents do\n      :unchanged\n    else\n      merged_po\n    end\n  end\n\n  defp read_contents_and_parse(path) do\n    contents = File.read!(path)\n    {contents, PO.parse_file!(path, file: path)}\n  end\n\n  # This function \"tags\" a {path, _} tuple in order to distinguish POT files\n  # that have been merged (one existed at `path` and there's a new one to put at\n  # `path` as well), POT files that exist but have no new counterpart (`{path,\n  # :existing}`) and new files that do not exist yet.\n  # These are marked as:\n  #   * {path, {:merged, _}} - one existed and there's a new one\n  #   * {path, {:unmerged, _}} - one existed, no new one\n  #   * {path, {:new, _}} - none existed, there's a new one\n  # Note that existing files with no new corresponding file are \"pruned\", for example,\n  # merged with an empty %Messages{} struct to remove obsolete message (see\n  # prune_unmerged/1), because the user could still have PO message that\n  # they manually inserted in that file.\n  defp tag_files({_path, {:merged, _}} = entry, _gettext_config), do: entry\n\n  defp tag_files({path, :existing}, gettext_config),\n    do: {path, {:unmerged, prune_unmerged(path, gettext_config)}}\n\n  defp tag_files({path, new_po}, _gettext_config), do: {path, {:new, new_po}}\n\n  # This function \"dumps\" merged files and unmerged files without any changes,\n  # and dumps new POT files adding an informative comment to them. This doesn't\n  # write anything to disk, it just returns `{path, contents}` tuples.\n  defp dump_tagged_file({path, {_tag, :unchanged}}), do: {path, :unchanged}\n  defp dump_tagged_file({path, {_tag, po}}), do: {path, {:changed, PO.compose(po)}}\n\n  defp prune_unmerged(path, gettext_config) do\n    merge_or_unchanged(path, %Messages{messages: []}, gettext_config)\n  end\n\n  # Merges a %Messages{} struct representing an existing POT file with an\n  # in-memory-only %Messages{} struct representing the new POT file.\n  # Made public for testing.\n  @doc false\n  def merge_template(existing, new, gettext_config) do\n    protected_pattern = gettext_config[:excluded_refs_from_purging]\n\n    # We go over the existing message in order so as to keep the existing\n    # order as much as possible.\n    old_and_merged =\n      Enum.flat_map(existing.messages, fn message ->\n        cond do\n          same = Messages.find(new, message) -> [merge_message(message, same)]\n          protected?(message, protected_pattern) -> [message]\n          autogenerated?(message) -> []\n          true -> [message]\n        end\n      end)\n\n    # We reject all messages that appear in `existing` so that we're left\n    # with the messages that only appear in `new`.\n    unique_new = Enum.reject(new.messages, &Messages.find(existing, &1))\n\n    messages = old_and_merged ++ unique_new\n\n    sort_by_msgid =\n      case gettext_config[:sort_by_msgid] || false do\n        val when val in [:case_sensitive, :case_insensitive, false] ->\n          val\n\n        true ->\n          IO.warn(\"\"\"\n          Passing \"true\" to the :sort_by_msgid option is deprecated. \\\n          Use :case_sensitive instead, or specify :case_insensitive.\\\n          \"\"\")\n\n          :case_sensitive\n      end\n\n    messages =\n      case sort_by_msgid do\n        :case_sensitive ->\n          Enum.sort_by(messages, &IO.chardata_to_string(&1.msgid))\n\n        :case_insensitive ->\n          Enum.sort_by(messages, &String.downcase(IO.chardata_to_string(&1.msgid)))\n\n        false ->\n          messages\n      end\n\n    %Messages{\n      messages: messages,\n      headers: existing.headers,\n      top_comments: existing.top_comments\n    }\n  end\n\n  defp merge_message(old, new) do\n    ensure_empty_msgstr!(old)\n    ensure_empty_msgstr!(new)\n\n    # Take all flags from `old` and only the `@extracted_messages_flag` flag from `new`\n    # to avoid re-adding manually removed flags.\n    flags =\n      if Message.has_flag?(new, @extracted_messages_flag) do\n        Message.append_flag(old, @extracted_messages_flag).flags\n      else\n        old.flags\n      end\n\n    old\n    |> Message.merge(new)\n    |> Map.merge(%{\n      flags: flags,\n      # We don't care about the references of the old message since the new\n      # in-memory message has all the actual and current references.\n      references: new.references,\n      extracted_comments: new.extracted_comments\n    })\n  end\n\n  defp ensure_empty_msgstr!(%Message.Singular{msgstr: msgstr} = message) do\n    unless blank?(msgstr) do\n      raise Error,\n            \"message with msgid '#{IO.iodata_to_binary(message.msgid)}' has a non-empty msgstr\"\n    end\n  end\n\n  defp ensure_empty_msgstr!(%Message.Plural{msgstr: msgstr} = message) do\n    if Enum.any?(Map.values(msgstr), &(not blank?(&1))) do\n      raise Error,\n            \"plural message with msgid '#{IO.iodata_to_binary(message.msgid)}' has a non-empty msgstr\"\n    end\n  end\n\n  defp ensure_empty_msgstr!(%Message.Plural{} = message) do\n    raise Error,\n          \"plural message with msgid '#{IO.iodata_to_binary(message.msgid)}' has a non-empty msgstr\"\n  end\n\n  defp blank?(str) when not is_nil(str), do: IO.iodata_length(str) == 0\n  defp blank?(_), do: true\n\n  @spec autogenerated?(message :: Message.t()) :: boolean\n  defp autogenerated?(message) do\n    Message.has_flag?(message, \"elixir-autogen\")\n  end\n\n  # A message that is protected from purging will never be removed by Gettext.\n  # Which messages are proteced can be configured using Mix.\n  @spec protected?(message :: Message.t(), protected_pattern :: Regex.t()) :: boolean\n  defp protected?(_t, nil),\n    do: false\n\n  defp protected?(%{references: []}, _pattern),\n    do: false\n\n  defp protected?(%{references: refs}, pattern),\n    do: refs |> List.flatten() |> Enum.any?(fn {path, _} -> Regex.match?(pattern, path) end)\nend\n"
  },
  {
    "path": "lib/gettext/extractor_agent.ex",
    "content": "defmodule Gettext.ExtractorAgent do\n  @moduledoc false\n\n  use Agent\n\n  require Logger\n\n  alias Expo.Message\n\n  @name __MODULE__\n\n  # :messages is a map where keys are Gettext backends and values\n  # are maps. In these maps, keys are domains and values are maps of\n  # message_id => message.\n  # :backends is just a list of backends that call `use Gettext`.\n  @initial_state %{\n    messages: %{},\n    backends: [],\n    extracting?: false\n  }\n\n  @spec start_link(keyword()) :: Agent.on_start()\n  def start_link([] = _opts) do\n    Agent.start_link(fn -> @initial_state end, name: @name)\n  end\n\n  @spec enable() :: :ok\n  def enable() do\n    Agent.update(@name, &put_in(&1.extracting?, true))\n  end\n\n  @spec disable() :: :ok\n  def disable() do\n    Agent.update(@name, &put_in(&1.extracting?, false))\n  end\n\n  @spec extracting?() :: boolean()\n  def extracting?() do\n    Agent.get(@name, & &1.extracting?)\n  end\n\n  @spec add_message(backend :: module(), domain :: String.t(), Message.t()) :: :ok\n  def add_message(backend, domain, message) do\n    key = Message.key(message)\n\n    Agent.cast(@name, fn state ->\n      # Initialize the given backend to an empty map if it wasn't there.\n      state = update_in(state.messages, &Map.put_new(&1, backend, %{}))\n\n      update_in(state.messages[backend][domain], fn messages ->\n        Map.update(messages || %{}, key, message, &merge_messages(&1, message))\n      end)\n    end)\n  end\n\n  def add_backend(backend) do\n    Agent.cast(@name, fn state ->\n      update_in(state.backends, &[backend | &1])\n    end)\n  end\n\n  def stop() do\n    Agent.stop(@name)\n  end\n\n  def pop_message(backends) do\n    Agent.get_and_update(@name, fn state ->\n      get_and_update_in(state.messages, &Map.split(&1, backends))\n    end)\n  end\n\n  def pop_backends(app) do\n    Agent.get_and_update(@name, fn state ->\n      get_and_update_in(state.backends, fn backends ->\n        Enum.split_with(backends, &(&1.__gettext__(:otp_app) == app))\n      end)\n    end)\n  end\n\n  defp merge_messages(%Message.Singular{} = message_1, %Message.Plural{} = message_2) do\n    # Flipping the arguments to make sure that the pluaral message (more information) is used as the base message\n    merge_messages(message_2, message_1)\n  end\n\n  defp merge_messages(%Message.Plural{} = message_1, %Message.Plural{} = message_2) do\n    # Make sure message choice is deterministic\n    [message_1, message_2] =\n      Enum.sort_by([message_1, message_2], &IO.iodata_to_binary(&1.msgid_plural))\n\n    if IO.iodata_to_binary(message_1.msgid_plural) != IO.iodata_to_binary(message_2.msgid_plural) do\n      Logger.warning(\"\"\"\n      Plural message for '#{IO.iodata_to_binary(message_1.msgid)}' is not matching:\n      Using '#{IO.iodata_to_binary(message_2.msgid_plural)}' instead of '#{IO.iodata_to_binary(message_1.msgid_plural)}'.\n      References: #{dump_references(message_1.references ++ message_2.references)}\\\n      \"\"\")\n    end\n\n    merge_messages_after_checks(message_1, message_2)\n  end\n\n  defp merge_messages(message_1, message_2), do: merge_messages_after_checks(message_1, message_2)\n\n  defp merge_messages_after_checks(message_1, message_2) do\n    message_1\n    |> Map.put(:references, message_1.references ++ message_2.references)\n    |> Map.put(\n      :extracted_comments,\n      Enum.uniq(message_1.extracted_comments ++ message_2.extracted_comments)\n    )\n  end\n\n  defp dump_references(references) do\n    references\n    |> List.flatten()\n    |> Enum.map(fn\n      {file, line} -> [file, \":\", Integer.to_string(line)]\n      file -> file\n    end)\n    |> Enum.intersperse(\", \")\n  end\nend\n"
  },
  {
    "path": "lib/gettext/fuzzy.ex",
    "content": "defmodule Gettext.Fuzzy do\n  @moduledoc false\n\n  alias Expo.Message\n\n  @type message_key :: {binary | nil, binary | {binary, binary}}\n\n  @doc \"\"\"\n  Returns a matcher function that takes two message keys and checks if they\n  match.\n\n  `String.jaro_distance/2` (which calculates the Jaro distance) is used to\n  measure the distance between the two messages. `threshold` is the minimum\n  distance that means a match. `{:match, distance}` is returned in case of a\n  match, `:nomatch` otherwise.\n  \"\"\"\n  @spec matcher(float) :: (message_key, message_key -> {:match, float} | :nomatch)\n  def matcher(threshold) do\n    fn old_key, new_key ->\n      distance = jaro_distance(old_key, new_key)\n      if distance >= threshold, do: {:match, distance}, else: :nomatch\n    end\n  end\n\n  @doc \"\"\"\n  Finds the Jaro distance between the msgids of two messages.\n\n  To mimic the behaviour of the `msgmerge` tool, this function only calculates\n  the Jaro distance of the msgids of the two messages, even if one (or both)\n  of them is a plural message.\n\n  As per `msgmerge`, the msgctxt of a message is completely ignored when\n  calculating the distance.\n  \"\"\"\n  @spec jaro_distance(message_key, message_key) :: float\n  def jaro_distance({_context1, key1}, {_context2, key2}) do\n    jaro_distance_on_key(key1, key2)\n  end\n\n  # Apparently, msgmerge only looks at the msgid when performing fuzzy\n  # matching. This means that if we have two plural messages with similar\n  # msgids but very different msgid_plurals, they'll still fuzzy match.\n  def jaro_distance_on_key(key1, key2) when is_binary(key1) and is_binary(key2),\n    do: String.jaro_distance(key1, key2)\n\n  def jaro_distance_on_key({key1, _}, key2) when is_binary(key2),\n    do: String.jaro_distance(key1, key2)\n\n  def jaro_distance_on_key(key1, {key2, _}) when is_binary(key1),\n    do: String.jaro_distance(key1, key2)\n\n  def jaro_distance_on_key({key1, _}, {key2, _}), do: String.jaro_distance(key1, key2)\n\n  @doc \"\"\"\n  Merges a message with the corresponding fuzzy match.\n\n  `new` is the newest message and `existing` is the existing message\n  that we use to populate the msgstr of the newest message.\n\n  Note that if `new` is a regular message, then the result will be a regular\n  message; if `new` is a plural message, then the result will be a\n  plural message.\n  \"\"\"\n  @spec merge(new :: Message.t(), existing :: Message.t()) :: Message.t()\n  def merge(new, existing) do\n    # Everything comes from \"new\", except for the msgstr and the comments.\n    new\n    |> Map.put(:comments, existing.comments)\n    |> merge_msgstr(existing)\n    |> Message.append_flag(\"fuzzy\")\n  end\n\n  defp merge_msgstr(%Message.Singular{} = new, %Message.Singular{} = existing),\n    do: %{new | msgstr: existing.msgstr}\n\n  defp merge_msgstr(%Message.Singular{} = new, %Message.Plural{} = existing),\n    do: %{new | msgstr: existing.msgstr[0]}\n\n  defp merge_msgstr(%Message.Plural{} = new, %Message.Singular{} = existing),\n    do: %{new | msgstr: Map.new(new.msgstr, fn {i, _} -> {i, existing.msgstr} end)}\n\n  defp merge_msgstr(%Message.Plural{} = new, %Message.Plural{} = existing),\n    do: %{new | msgstr: existing.msgstr}\nend\n"
  },
  {
    "path": "lib/gettext/interpolation/default.ex",
    "content": "defmodule Gettext.Interpolation.Default do\n  @moduledoc \"\"\"\n  Default implementation for the `Gettext.Interpolation` behaviour.\n\n  Replaces `%{binding_name}` with the string value of the `binding_name` binding.\n  \"\"\"\n\n  @behaviour Gettext.Interpolation\n\n  @typedoc \"\"\"\n  Something that can be interpolated.\n\n  It's either a string (a literal) or an atom (representing a binding name).\n  \"\"\"\n  @type interpolatable() :: [String.t() | atom()]\n\n  # Extracts interpolations from a given string.\n\n  # This function extracts all interpolations in the form `%{interpolation}`\n  # contained inside `str`, converts them to atoms and then returns a list of\n  # string and interpolation keys.\n  @doc false\n  @spec to_interpolatable(String.t()) :: interpolatable()\n  def to_interpolatable(string) when is_binary(string) do\n    start_pattern = :binary.compile_pattern(\"%{\")\n    end_pattern = :binary.compile_pattern(\"}\")\n\n    string\n    |> to_interpolatable(_current = \"\", _acc = [], start_pattern, end_pattern)\n    |> Enum.reverse()\n  end\n\n  defp to_interpolatable(string, current, acc, start_pattern, end_pattern) do\n    case :binary.split(string, start_pattern) do\n      # If we have one element, no %{ was found so this is the final part of the\n      # string.\n      [rest] ->\n        prepend_if_not_empty(current <> rest, acc)\n\n      # If we found a %{ but it's followed by an immediate }, then we just\n      # append %{} to the current string and keep going.\n      [before, \"}\" <> rest] ->\n        new_current = current <> before <> \"%{}\"\n        to_interpolatable(rest, new_current, acc, start_pattern, end_pattern)\n\n      # Otherwise, we found the start of a binding.\n      [before, binding_and_rest] ->\n        case :binary.split(binding_and_rest, end_pattern) do\n          # If we don't find the end of this binding, it means we're at a string\n          # like \"foo %{ no end\". In this case we consider no bindings to be\n          # there.\n          [_] ->\n            [current <> string | acc]\n\n          # This is the case where we found a binding, so we put it in the acc\n          # and keep going.\n          [binding, rest] ->\n            new_acc = [String.to_atom(binding) | prepend_if_not_empty(before, acc)]\n            to_interpolatable(rest, \"\", new_acc, start_pattern, end_pattern)\n        end\n    end\n  end\n\n  defp prepend_if_not_empty(\"\", list), do: list\n  defp prepend_if_not_empty(string, list), do: [string | list]\n\n  @doc \"\"\"\n  Interpolate a message or interpolatable with the given bindings.\n\n  Implementation of the `c:Gettext.Interpolation.runtime_interpolate/2` callback.\n\n  This function takes a message and some bindings and returns an `{:ok,\n  interpolated_string}` tuple if interpolation is successful. If it encounters\n  a binding in the message that is missing from `bindings`, it returns\n  `{:missing_bindings, incomplete_string, missing_bindings}` where\n  `incomplete_string` is the string with only the present bindings interpolated\n  and `missing_bindings` is a list of atoms representing bindings that are in\n  `interpolatable` but not in `bindings`.\n\n  ## Examples\n\n      iex> msgid = \"Hello %{name}, you have %{count} unread messages\"\n      iex> good_bindings = %{name: \"José\", count: 3}\n      iex> Gettext.Interpolation.Default.runtime_interpolate(msgid, good_bindings)\n      {:ok, \"Hello José, you have 3 unread messages\"}\n      iex> Gettext.Interpolation.Default.runtime_interpolate(msgid, %{name: \"José\"})\n      {:missing_bindings, \"Hello José, you have %{count} unread messages\", [:count]}\n\n      iex> msgid = \"Hello %{name}, you have %{count} unread messages\"\n      iex> interpolatable = Gettext.Interpolation.Default.to_interpolatable(msgid)\n      iex> good_bindings = %{name: \"José\", count: 3}\n      iex> Gettext.Interpolation.Default.runtime_interpolate(interpolatable, good_bindings)\n      {:ok, \"Hello José, you have 3 unread messages\"}\n      iex> Gettext.Interpolation.Default.runtime_interpolate(interpolatable, %{name: \"José\"})\n      {:missing_bindings, \"Hello José, you have %{count} unread messages\", [:count]}\n\n  \"\"\"\n  @impl true\n  def runtime_interpolate(message, bindings)\n\n  def runtime_interpolate(message, %{} = bindings) when is_binary(message) do\n    message |> to_interpolatable() |> runtime_interpolate(bindings)\n  end\n\n  def runtime_interpolate(interpolatable, %{} = bindings) when is_list(interpolatable) do\n    interpolate(interpolatable, bindings, [], [])\n  end\n\n  defp interpolate([string | segments], bindings, strings, missing) when is_binary(string) do\n    interpolate(segments, bindings, [string | strings], missing)\n  end\n\n  defp interpolate([atom | segments], bindings, strings, missing) when is_atom(atom) do\n    case bindings do\n      %{^atom => value} ->\n        interpolate(segments, bindings, [to_string(value) | strings], missing)\n\n      %{} ->\n        strings = [\"%{\" <> Atom.to_string(atom) <> \"}\" | strings]\n        interpolate(segments, bindings, strings, [atom | missing])\n    end\n  end\n\n  defp interpolate([], _bindings, strings, []) do\n    {:ok, IO.iodata_to_binary(Enum.reverse(strings))}\n  end\n\n  defp interpolate([], _bindings, strings, missing) do\n    missing = missing |> Enum.reverse() |> Enum.uniq()\n    {:missing_bindings, IO.iodata_to_binary(Enum.reverse(strings)), missing}\n  end\n\n  # Returns all the interpolation keys contained in the given string or list of\n  # segments.\n\n  # This function returns a list of all the interpolation keys (patterns in the\n  # form `%{interpolation}`) contained in its argument.\n\n  # If the argument is a segment list, that is, a list of strings and atoms where\n  # atoms represent interpolation keys, then only the atoms in the list are\n  # returned.\n  @doc false\n  @spec keys(String.t() | interpolatable()) :: [atom()]\n  def keys(string_or_interpolatable)\n\n  def keys(string) when is_binary(string), do: string |> to_interpolatable() |> keys()\n\n  def keys(interpolatable) when is_list(interpolatable),\n    do: interpolatable |> Enum.filter(&is_atom/1) |> Enum.uniq()\n\n  @doc \"\"\"\n  Compiles a static message to interpolate with dynamic bindings.\n\n  Implementation of the `c:Gettext.Interpolation.compile_interpolate/3` macro callback.\n\n  Takes a static message and some dynamic bindings. The generated\n  code will return an `{:ok, interpolated_string}` tuple if the interpolation\n  is successful. If it encounters a binding in the message that is missing from\n  `bindings`, it returns `{:missing_bindings, incomplete_string, missing_bindings}`,\n  where `incomplete_string` is the string with only the present bindings interpolated\n  and `missing_bindings` is a list of atoms representing bindings that are in\n  `interpolatable` but not in `bindings`.\n  \"\"\"\n  @impl true\n  defmacro compile_interpolate(message_type, message, bindings) do\n    unless is_binary(message) do\n      raise \"\"\"\n      #{inspect(__MODULE__)}.compile_interpolate/2 can only be used at compile time with \\\n      static messages. Alternatively, use #{inspect(__MODULE__)}.runtime_interpolate/2.\n      \"\"\"\n    end\n\n    interpolatable = to_interpolatable(message)\n    keys = keys(interpolatable)\n    match_clause = match_clause(keys)\n    compile_string = compile_string(interpolatable)\n\n    case {keys, message_type} do\n      # If no keys are in the message, the message can be returned without interpolation\n      {[], _message_type} ->\n        quote do: {:ok, unquote(message)}\n\n      # If the message only contains the key `count` and it is a plural message,\n      # gettext ensures that `count` is always set. Therefore the dynamic interpolation\n      # will never be needed.\n      {[:count], :plural_translation} ->\n        quote do\n          unquote(match_clause) = unquote(bindings)\n          {:ok, unquote(compile_string)}\n        end\n\n      {_keys, _message_type} ->\n        quote generated: true do\n          case unquote(bindings) do\n            unquote(match_clause) ->\n              {:ok, unquote(compile_string)}\n\n            %{} = other_bindings ->\n              unquote(__MODULE__).runtime_interpolate(unquote(interpolatable), other_bindings)\n          end\n        end\n    end\n  end\n\n  # Compiles a list of atoms into a \"match\" map. For example `[:foo, :bar]` gets\n  # compiled to `%{foo: foo, bar: bar}`. All generated variables are under the\n  # current `__MODULE__`.\n  defp match_clause(keys) do\n    {:%{}, [], Enum.map(keys, &{&1, Macro.var(&1, __MODULE__)})}\n  end\n\n  # Compiles a string into a binary with `%{var}` patterns turned into `var`\n  # variables, namespaced inside the current `__MODULE__`.\n  defp compile_string(interpolatable) do\n    parts =\n      Enum.map(interpolatable, fn\n        key when is_atom(key) ->\n          quote do: to_string(unquote(Macro.var(key, __MODULE__))) :: binary\n\n        str ->\n          str\n      end)\n\n    {:<<>>, [], parts}\n  end\n\n  @doc \"\"\"\n  Implementation of `c:Gettext.Interpolation.message_format/0`.\n\n  ## Examples\n\n      iex> Gettext.Interpolation.Default.message_format()\n      \"elixir-format\"\n\n  \"\"\"\n  @impl true\n  def message_format, do: \"elixir-format\"\nend\n"
  },
  {
    "path": "lib/gettext/interpolation.ex",
    "content": "defmodule Gettext.Interpolation do\n  @moduledoc \"\"\"\n  Behaviour to provide Gettext string interpolation.\n\n  By default, Gettext uses `Gettext.Interpolation.Default` as the interpolation module.\n  \"\"\"\n\n  @typedoc since: \"0.19.0\"\n  @type translation_type() :: :translation | :plural_translation\n\n  @typedoc since: \"0.22.0\"\n  @type bindings() :: %{optional(atom()) => term()}\n\n  @doc \"\"\"\n  Called to perform interpolation *at runtime*.\n\n  If successful, should return `{:ok, interpolated_string}`. If there\n  are missing bindings, should return `{:missing_bindings, partially_interpolated, missing}`\n  where `partially_interpolated` is a string with the available bindings interpolated.\n  \"\"\"\n  @doc since: \"0.19.0\"\n  @callback runtime_interpolate(message :: String.t(), bindings()) ::\n              {:ok, String.t()}\n              | {:missing_bindings, partially_interpolated_message :: String.t(),\n                 missing_bindings :: [atom()]}\n\n  @doc \"\"\"\n  Called to perform interpolation *at compile time*.\n  \"\"\"\n  @doc since: \"0.19.0\"\n  @macrocallback compile_interpolate(translation_type(), message :: String.t(), bindings()) ::\n                   Macro.t()\n\n  @doc \"\"\"\n  Defines the Gettext message format to be used when extracting.\n\n  The default interpolation module that ships with Gettext uses `\"elixir-format\"`.\n\n  See the [GNU Gettext\n  documentation](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#index-msgstr).\n  \"\"\"\n  @doc since: \"0.19.0\"\n  @callback message_format() :: String.t()\nend\n"
  },
  {
    "path": "lib/gettext/macros.ex",
    "content": "defmodule Gettext.Macros do\n  @moduledoc \"\"\"\n  Macros used by Gettext to provide the gettext family of functions.\n\n  *Available since v0.26.0.*\n\n  Macros enable users to use gettext and get **automatic extraction** of translations.\n  See `Gettext` for more information.\n\n  The macros in this module *that don't end with `_with_backend`* are imported\n  every time you call:\n\n      use Gettext, backend: MyApp.Gettext\n\n  ### Explicit backend\n\n  If you need to use the macros here with an explicit backend and you want extraction\n  to work, you can use the `_with_backend` versions of the macros in this module explicitly\n  instead.\n\n      defmodule MyApp.Gettext do\n        use Gettext, otp_app: :my_app\n      end\n\n      defmodule MyApp.Controller do\n        require Gettext.Macros\n\n        def index(conn, _params) do\n          Gettext.Macros.gettext_with_backend(MyApp.Gettext, \"Hello, world!\")\n        end\n      end\n\n  \"\"\"\n\n  @moduledoc since: \"0.26.0\"\n\n  alias Gettext.Extractor\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      dpgettext_noop(\"errors\", \"Home page\", \"Error found!\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro dpgettext_noop(domain, msgctxt, msgid) do\n    extract_singular_translation(__CALLER__, backend(__CALLER__), domain, msgctxt, msgid)\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      dgettext_noop(\"errors\", \"Error found!\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro dgettext_noop(domain, msgid) do\n    extract_singular_translation(__CALLER__, backend(__CALLER__), domain, _msgctxt = nil, msgid)\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      gettext_noop(\"Error found!\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro gettext_noop(msgid) do\n    extract_singular_translation(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      _msgctxt = nil,\n      msgid\n    )\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      pgettext_noop(\"Error found!\", \"Home page\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro pgettext_noop(msgid, context) do\n    extract_singular_translation(__CALLER__, backend(__CALLER__), :default, context, msgid)\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      dpngettext_noop(\"errors\", \"Home page\", \"Error found!\", \"Errors found!\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro dpngettext_noop(domain, msgctxt, msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      msgctxt,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns\n  `{msgid, msgid_plural}`.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value of this macro is `{msgid,\n  msgid_plural}`.\n\n  ## Examples\n\n      my_fun = fn {msgid, msgid_plural} ->\n        # do something with msgid and msgid_plural\n      end\n\n      my_fun.(dngettext_noop(\"errors\", \"One error\", \"%{count} errors\"))\n\n  \"\"\"\n  defmacro dngettext_noop(domain, msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  @doc \"\"\"\n  Marks the given message for extraction and returns it unchanged.\n\n  This macro can be used to mark a message for extraction when `mix\n  gettext.extract` is run. The return value is the given string, so that this\n  macro can be used seamlessly in place of the string to extract.\n\n  ## Examples\n\n      pngettext_noop(\"Home page\", \"Error found!\", \"Errors found!\")\n      #=> \"Error found!\"\n\n  \"\"\"\n  defmacro pngettext_noop(msgctxt, msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      msgctxt,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  @doc \"\"\"\n  Same as `dngettext_noop(\"default\", msgid, mgsid_plural)`, but will use a\n  per-backend configured default domain if provided.\n  \"\"\"\n  defmacro ngettext_noop(msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given `msgid` with a given context (`msgctxt`) in the given `domain`.\n\n  `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.dpgettext/5`.\n  \"\"\"\n  defmacro dpgettext(domain, msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      msgctxt,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given `msgid` in the given `domain`.\n\n  `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.dgettext/4`.\n  \"\"\"\n  defmacro dgettext(domain, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      _msgctxt = nil,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given `msgid` with the given context (`msgctxt`).\n\n  `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.pgettext/4`.\n  \"\"\"\n  defmacro pgettext(msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      msgctxt,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `dgettext(\"default\", msgid, %{})`, but will use a per-backend\n  configured default domain if provided.\n\n  See also `Gettext.gettext/3`.\n  \"\"\"\n  defmacro gettext(msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given plural message (`msgid` + `msgid_plural`) with the given context (`msgctxt`)\n  in the given `domain`.\n\n  `n` is an integer used to determine how to pluralize the\n  message. `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.dpngettext/7`.\n  \"\"\"\n  defmacro dpngettext(domain, msgctxt, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      msgctxt,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given plural message (`msgid` + `msgid_plural`) in the\n  given `domain`.\n\n  `n` is an integer used to determine how to pluralize the\n  message. `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.dngettext/6`.\n  \"\"\"\n  defmacro dngettext(domain, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      domain,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `dngettext(\"default\", msgid, msgid_plural, n, bindings)`, but will\n  use a per-backend configured default domain if provided.\n\n  See also `Gettext.ngettext/5`.\n  \"\"\"\n  defmacro ngettext(msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Translates the given plural message (`msgid` + `msgid_plural`) with the given context (`msgctxt`).\n\n  `n` is an integer used to determine how to pluralize the\n  message. `bindings` is a map of bindings to support interpolation.\n\n  See also `Gettext.pngettext/6`.\n  \"\"\"\n  defmacro pngettext(msgctxt, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend(__CALLER__),\n      _domain = :default,\n      msgctxt,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Stores an \"extracted comment\" for the next message.\n\n  This macro can be used to add comments (Gettext refers to such\n  comments as *extracted comments*) to the next message that will\n  be extracted. Extracted comments will be prefixed with `#.` in POT\n  files.\n\n  Calling this function multiple times will accumulate the comments;\n  when another Gettext macro (such as `gettext/2`) is called,\n  the comments will be extracted and attached to that message, and\n  they will be flushed so as to start again.\n\n  This macro always returns `:ok`.\n\n  ## Examples\n\n      gettext_comment(\"The next message is awesome\")\n      gettext_comment(\"Another comment for the next message\")\n      gettext(\"The awesome message\")\n\n  \"\"\"\n  defmacro gettext_comment(comment) do\n    comment = expand_to_binary(comment, \"comment\", __CALLER__)\n    append_extracted_comment(comment)\n    :ok\n  end\n\n  ## Singular no-op macros (with backend).\n\n  @doc \"\"\"\n  Same as `dpgettext_noop/3`, but takes an explicit backend.\n  \"\"\"\n  defmacro dpgettext_noop_with_backend(backend, domain, msgctxt, msgid) do\n    extract_singular_translation(__CALLER__, backend, domain, msgctxt, msgid)\n  end\n\n  @doc \"\"\"\n  Same as `dgettext_noop/2`, but takes an explicit backend.\n  \"\"\"\n  defmacro dgettext_noop_with_backend(backend, domain, msgid) do\n    extract_singular_translation(__CALLER__, backend, domain, _msgctxt = nil, msgid)\n  end\n\n  @doc \"\"\"\n  Same as `pgettext_noop/2`, but takes an explicit backend.\n  \"\"\"\n  defmacro pgettext_noop_with_backend(backend, msgctxt, msgid) do\n    extract_singular_translation(__CALLER__, backend, _domain = :default, msgctxt, msgid)\n  end\n\n  @doc \"\"\"\n  Same as `gettext_noop/1`, but takes an explicit backend.\n  \"\"\"\n  defmacro gettext_noop_with_backend(backend, msgid) do\n    extract_singular_translation(__CALLER__, backend, _domain = :default, _msgctxt = nil, msgid)\n  end\n\n  ## Plural no-op macros (with backend).\n\n  @doc \"\"\"\n  Same as `dpngettext_noop/4`, but takes an explicit backend.\n  \"\"\"\n  defmacro dpngettext_noop_with_backend(backend, domain, msgctxt, msgid, msgid_plural) do\n    extract_plural_translation(__CALLER__, backend, domain, msgctxt, msgid, msgid_plural)\n  end\n\n  @doc \"\"\"\n  Same as `dngettext_noop/3`, but takes an explicit backend.\n  \"\"\"\n  defmacro dngettext_noop_with_backend(backend, domain, msgid, msgid_plural) do\n    extract_plural_translation(__CALLER__, backend, domain, _msgctxt = nil, msgid, msgid_plural)\n  end\n\n  @doc \"\"\"\n  Same as `pngettext_noop/3`, but takes an explicit backend.\n  \"\"\"\n  defmacro pngettext_noop_with_backend(backend, msgctxt, msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      msgctxt,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  @doc \"\"\"\n  Same as `ngettext_noop/2`, but takes an explicit backend.\n  \"\"\"\n  defmacro ngettext_noop_with_backend(backend, msgid, msgid_plural) do\n    extract_plural_translation(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural\n    )\n  end\n\n  ## Singular macros (with backend).\n\n  @doc \"\"\"\n  Same as `dpgettext/4`, but takes an explicit backend.\n  \"\"\"\n  defmacro dpgettext_with_backend(backend, domain, msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(__CALLER__, backend, domain, msgctxt, msgid, bindings)\n  end\n\n  @doc \"\"\"\n  Same as `dgettext/3`, but takes an explicit backend.\n  \"\"\"\n  defmacro dgettext_with_backend(backend, domain, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(__CALLER__, backend, domain, _msgctxt = nil, msgid, bindings)\n  end\n\n  @doc \"\"\"\n  Same as `pgettext/3`, but takes an explicit backend.\n  \"\"\"\n  defmacro pgettext_with_backend(backend, msgctxt, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      msgctxt,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `gettext/2`, but takes an explicit backend.\n  \"\"\"\n  defmacro gettext_with_backend(backend, msgid, bindings \\\\ Macro.escape(%{})) do\n    singular_extract_and_translate(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `dpngettext/6`, but takes an explicit backend.\n  \"\"\"\n  defmacro dpngettext_with_backend(\n             backend,\n             domain,\n             msgctxt,\n             msgid,\n             msgid_plural,\n             n,\n             bindings \\\\ Macro.escape(%{})\n           ) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend,\n      domain,\n      msgctxt,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `dngettext/5`, but takes an explicit backend.\n  \"\"\"\n  defmacro dngettext_with_backend(\n             backend,\n             domain,\n             msgid,\n             msgid_plural,\n             n,\n             bindings \\\\ Macro.escape(%{})\n           ) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend,\n      domain,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `pngettext/5`, but takes an explicit backend.\n  \"\"\"\n  defmacro pngettext_with_backend(\n             backend,\n             msgctxt,\n             msgid,\n             msgid_plural,\n             n,\n             bindings \\\\ Macro.escape(%{})\n           ) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      msgctxt,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  @doc \"\"\"\n  Same as `ngettext/4`, but takes an explicit backend.\n  \"\"\"\n  defmacro ngettext_with_backend(backend, msgid, msgid_plural, n, bindings \\\\ Macro.escape(%{})) do\n    plural_extract_and_translate(\n      __CALLER__,\n      backend,\n      _domain = :default,\n      _msgctxt = nil,\n      msgid,\n      msgid_plural,\n      n,\n      bindings\n    )\n  end\n\n  ## Helpers\n\n  defp extract_singular_translation(env, backend, domain, msgctxt, msgid) do\n    backend = expand_backend(backend, env)\n    domain = expand_domain(domain, env)\n    msgid = expand_to_binary(msgid, \"msgid\", env)\n    msgctxt = expand_to_binary(msgctxt, \"msgctxt\", env)\n\n    if Extractor.extracting?() do\n      Extractor.extract(\n        env,\n        backend,\n        domain,\n        msgctxt,\n        msgid,\n        get_and_flush_extracted_comments()\n      )\n    end\n\n    msgid\n  end\n\n  defp extract_plural_translation(env, backend, domain, msgctxt, msgid, msgid_plural) do\n    backend = expand_backend(backend, env)\n    domain = expand_domain(domain, env)\n    msgid = expand_to_binary(msgid, \"msgid\", env)\n    msgctxt = expand_to_binary(msgctxt, \"msgctxt\", env)\n    msgid_plural = expand_to_binary(msgid_plural, \"msgid_plural\", env)\n\n    if Extractor.extracting?() do\n      Extractor.extract(\n        env,\n        backend,\n        domain,\n        msgctxt,\n        {msgid, msgid_plural},\n        get_and_flush_extracted_comments()\n      )\n    end\n\n    {msgid, msgid_plural}\n  end\n\n  defp singular_extract_and_translate(env, backend, domain, msgctxt, msgid, bindings) do\n    domain = expand_domain(domain, env)\n    msgid = extract_singular_translation(env, backend, domain, msgctxt, msgid)\n\n    quote do\n      Gettext.dpgettext(\n        unquote(backend),\n        unquote(domain),\n        unquote(msgctxt),\n        unquote(msgid),\n        unquote(bindings)\n      )\n    end\n  end\n\n  defp plural_extract_and_translate(\n         env,\n         backend,\n         domain,\n         msgctxt,\n         msgid,\n         msgid_plural,\n         n,\n         bindings\n       ) do\n    domain = expand_domain(domain, env)\n\n    {msgid, msgid_plural} =\n      extract_plural_translation(env, backend, domain, msgctxt, msgid, msgid_plural)\n\n    quote do\n      Gettext.dpngettext(\n        unquote(backend),\n        unquote(domain),\n        unquote(msgctxt),\n        unquote(msgid),\n        unquote(msgid_plural),\n        unquote(n),\n        unquote(bindings)\n      )\n    end\n  end\n\n  defp expand_domain(:default, _env), do: :default\n  defp expand_domain(domain, env), do: expand_to_binary(domain, \"domain\", env)\n\n  defp backend(%Macro.Env{} = env) do\n    Module.get_attribute(env.module, :__gettext_backend__) ||\n      raise \"\"\"\n      in order to use Gettext.Macros, you must:\n\n          use Gettext, backend: ...\n\n      \"\"\"\n  end\n\n  defp expand_to_binary(term, what, %Macro.Env{} = env)\n       when what in ~w(domain msgctxt msgid msgid_plural comment) do\n    raiser = fn term ->\n      gettext_module = Module.get_attribute(env.module, :__gettext_backend__)\n\n      raise ArgumentError, \"\"\"\n      Gettext macros expect message keys (msgid and msgid_plural),\n      domains, and comments to expand to strings at compile-time, but the given #{what}\n      doesn't. This is what the macro received:\n\n      #{inspect(term)}\n\n      Dynamic messages should be avoided as they limit Gettext's\n      ability to extract messages from your source code. If you are\n      sure you need dynamic lookup, you can use the functions in the Gettext\n      module:\n\n          string = \"hello world\"\n          Gettext.gettext(#{if(gettext_module, do: inspect(gettext_module), else: \"backend\")}, string)\n      \"\"\"\n    end\n\n    validated_expand_to_binary(term, env, raiser)\n  end\n\n  defp validated_expand_to_binary({:<>, _, pieces}, env, raiser) do\n    Enum.map_join(pieces, &validated_expand_to_binary(&1, env, raiser))\n  end\n\n  defp validated_expand_to_binary({:<<>>, _, pieces}, env, raiser) do\n    Enum.map_join(pieces, &validated_expand_to_binary(&1, env, raiser))\n  end\n\n  # We support nil too in order to fall back to a nil context and always use the *p\n  # variants of the Gettext macros.\n  defp validated_expand_to_binary(term, _env, _raiser)\n       when is_binary(term) or is_nil(term) do\n    term\n  end\n\n  defp validated_expand_to_binary(term, env, raiser) do\n    case Macro.expand(term, env) do\n      term when is_binary(term) or is_nil(term) ->\n        term\n\n      other ->\n        raiser.(other)\n    end\n  end\n\n  defp expand_backend(term, %Macro.Env{} = env) do\n    case Macro.expand(term, env) do\n      term when is_atom(term) and term not in [nil, false, true] ->\n        term\n\n      _other ->\n        raise ArgumentError, \"\"\"\n        Gettext.Macros macros (that end with \"_with_backend\") expect the backend argument\n        to be an atom at compile-time, but the given term doesn't. This is what the macro\n        received:\n\n        #{inspect(term)}\n\n        Dynamic messages should be avoided as they limit Gettext's\n        ability to extract messages from your source code. If you are\n        sure you need dynamic lookup, you can use the functions in the Gettext\n        module:\n\n            string = \"hello world\"\n            Gettext.gettext(backend, string)\n        \"\"\"\n    end\n  end\n\n  defp append_extracted_comment(comment) do\n    existing = Process.get(:gettext_comments, [])\n    Process.put(:gettext_comments, [\" \" <> comment | existing])\n    :ok\n  end\n\n  defp get_and_flush_extracted_comments() do\n    Enum.reverse(Process.delete(:gettext_comments) || [])\n  end\nend\n"
  },
  {
    "path": "lib/gettext/merger.ex",
    "content": "defmodule Gettext.Merger do\n  @moduledoc false\n\n  alias Expo.PO\n  alias Expo.Message\n  alias Expo.Messages\n  alias Gettext.Fuzzy\n  alias Gettext.Plural\n\n  @new_po_informative_comment \"\"\"\n  # \"msgid\"s in this file come from POT (.pot) files.\n  ##\n  ## Do not add, change, or remove \"msgid\"s manually here as\n  ## they're tied to the ones in the corresponding POT file\n  ## (with the same domain).\n  ##\n  ## Use \"mix gettext.extract --merge\" or \"mix gettext.merge\"\n  ## to merge POT files into PO files.\n  \"\"\"\n\n  @doc \"\"\"\n  Merges two `Gettext.PO` structs representing a PO file and an updated POT (or\n  PO) file into a new `Gettext.PO` struct.\n\n  `old` is an existing PO file (that contains messages) which will be\n  \"updated\" with the messages in the `new` POT or PO file. messages in\n  `old` will kept as long as they match with messages in `new`; all other\n  messages will be discarded (as `new` is considered to be the reference).\n\n  The `Gettext.PO` struct that this function returns is *always* meant to be a PO\n  file, not a POT file.\n\n  `new` can be:\n\n    * a POT file (usually created or updated by the `mix gettext.extract` task) or\n    * a newly created PO file with up-to-date source references (but old messages)\n\n  Note that all translator comments in `new` will be discarded in favour of the\n  ones in `old`. Reference comments and extracted comments will be taken from\n  `new` instead.\n\n  The following rules are observed:\n\n    * matching messages are merged as follows:\n      * existing msgstr are preserved (the ones in the POT file are empty anyways)\n      * existing translator comments are preserved (there are no translator\n        comments in POT files)\n      * existing extracted comments are replaced by new extracted comments\n      * existing references are discarded (as they're now outdated) and replaced\n        by the references in the POT file\n\n  \"\"\"\n  @spec merge(Messages.t(), Messages.t(), String.t(), Keyword.t(), Keyword.t()) ::\n          {Messages.t(), map()}\n  def merge(%Messages{} = old, %Messages{} = new, locale, opts, gettext_config)\n      when is_binary(locale) and is_list(opts) do\n    opts = put_plural_forms_opt(opts, old, locale)\n\n    stats = %{new: 0, exact_matches: 0, fuzzy_matches: 0, removed: 0, marked_as_obsolete: 0}\n\n    {messages, stats} = merge_messages(old.messages, new.messages, opts, gettext_config, stats)\n\n    po = %Messages{\n      top_comments: old.top_comments,\n      headers: old.headers,\n      file: old.file,\n      messages: messages\n    }\n\n    {po, stats}\n  end\n\n  defp merge_messages(old, new, opts, gettext_config, stats) do\n    fuzzy? = Keyword.fetch!(opts, :fuzzy)\n    fuzzy_threshold = Keyword.fetch!(opts, :fuzzy_threshold)\n    plural_forms = Keyword.fetch!(opts, :plural_forms)\n    custom_flags_to_keep = Keyword.get(gettext_config, :custom_flags_to_keep, [])\n\n    old = Map.new(old, &{Message.key(&1), &1})\n\n    {messages, {stats, unused}} =\n      Enum.map_reduce(new, {stats, _unused = old}, fn message, {stats_acc, unused} ->\n        key = Message.key(message)\n        message = adjust_number_of_plural_forms(message, plural_forms)\n\n        case Map.fetch(old, key) do\n          {:ok, exact_match} ->\n            stats = update_in(stats_acc.exact_matches, &(&1 + 1))\n\n            {merge_two_messages(exact_match, message, custom_flags_to_keep),\n             {stats, Map.delete(unused, key)}}\n\n          :error when fuzzy? ->\n            case maybe_merge_fuzzy(message, old, key, fuzzy_threshold) do\n              {:matched, match, fuzzy_merged} ->\n                stats_acc = update_in(stats_acc.fuzzy_matches, &(&1 + 1))\n                unused = Map.delete(unused, Message.key(match))\n\n                fuzzy_merged =\n                  if Keyword.get(opts, :store_previous_message_on_fuzzy_match, false) do\n                    Map.update!(fuzzy_merged, :previous_messages, fn previous ->\n                      Enum.uniq_by(previous ++ [match], &Message.key/1)\n                    end)\n                  else\n                    fuzzy_merged\n                  end\n\n                {fuzzy_merged, {stats_acc, unused}}\n\n              :nomatch ->\n                stats_acc = update_in(stats_acc.new, &(&1 + 1))\n                {message, {stats_acc, unused}}\n            end\n\n          :error ->\n            stats_acc = update_in(stats_acc.new, &(&1 + 1))\n            {message, {stats_acc, unused}}\n        end\n      end)\n\n    messages = Enum.map(messages, &%{&1 | obsolete: false})\n\n    {messages, stats} =\n      case Keyword.get(opts, :on_obsolete, :delete) do\n        :mark_as_obsolete ->\n          {messages ++ (unused |> Map.values() |> Enum.map(&%{&1 | obsolete: true})),\n           put_in(stats.marked_as_obsolete, map_size(unused))}\n\n        :delete ->\n          {messages, put_in(stats.removed, map_size(unused))}\n      end\n\n    {messages, stats}\n  end\n\n  defp adjust_number_of_plural_forms(%Message.Plural{} = message, plural_forms)\n       when plural_forms > 0 do\n    new_msgstr = Map.new(0..(plural_forms - 1), &{&1, [\"\"]})\n    %{message | msgstr: new_msgstr}\n  end\n\n  defp adjust_number_of_plural_forms(%Message.Singular{} = message, _plural_forms) do\n    message\n  end\n\n  defp maybe_merge_fuzzy(message, old, key, fuzzy_threshold) do\n    if matched = find_fuzzy_match(old, key, fuzzy_threshold) do\n      {:matched, matched, Fuzzy.merge(message, matched)}\n    else\n      :nomatch\n    end\n  end\n\n  defp find_fuzzy_match(messages, key, threshold) do\n    matcher = Fuzzy.matcher(threshold)\n\n    candidates =\n      for {k, message} <- messages,\n          match = matcher.(k, key),\n          match != :nomatch,\n          do: {message, match}\n\n    if candidates == [] do\n      nil\n    else\n      {message, _match} = Enum.max_by(candidates, fn {_t, {:match, distance}} -> distance end)\n      message\n    end\n  end\n\n  # msgid, msgid_plural: they're the same\n  # msgctxt: it's the same, even if it's not present (nil)\n  # msgstr: new.msgstr should be empty since it comes from a POT file\n  # comments: new has no translator comments as it comes from POT\n  # extracted_comments: we should take the new most recent ones\n  # flags: we should take the new flags and preserve the fuzzy flag\n  # references: new contains the updated and most recent references\n\n  defp merge_two_messages(old, new, custom_flags_to_keep) do\n    old\n    |> Message.merge(new)\n    |> Map.merge(%{\n      comments: old.comments,\n      extracted_comments: new.extracted_comments,\n      flags: merge_flags(old, new, custom_flags_to_keep),\n      references: new.references\n    })\n  end\n\n  defp merge_flags(old_message, new_message, custom_flags_to_keep) do\n    # Force the \"fuzzy\" flag.\n    flags_to_keep = Enum.uniq([\"fuzzy\" | custom_flags_to_keep])\n\n    %{flags: flags} =\n      Enum.reduce(flags_to_keep, new_message, fn flag, message ->\n        if Message.has_flag?(old_message, flag) do\n          Message.append_flag(message, flag)\n        else\n          message\n        end\n      end)\n\n    flags\n  end\n\n  @doc \"\"\"\n  Returns the contents of a new PO file to be written at `po_file` from the POT\n  template in `pot_file`.\n\n  The new PO file will have:\n\n    * the `Language` header set based on the locale (extracted from the path)\n    * the messages of the POT file (no merging is needed as there are no\n      messages in the PO file)\n\n  Comments in `pot_file` that start with `##` will be discarded and not copied\n  over the new PO file as they're meant to be comments generated by tools or\n  comments directed to developers.\n  \"\"\"\n  def new_po_file(po_file, pot_file, locale, opts) when is_binary(locale) and is_list(opts) do\n    pot = PO.parse_file!(pot_file)\n    opts = put_plural_forms_opt(opts, pot, locale)\n    plural_forms = Keyword.fetch!(opts, :plural_forms)\n    plural_forms_header = Keyword.fetch!(opts, :plural_forms_header)\n\n    po = %Messages{\n      top_comments: String.split(@new_po_informative_comment, \"\\n\", trim: true),\n      headers: headers_for_new_po_file(locale, plural_forms_header),\n      file: po_file,\n      messages: Enum.map(pot.messages, &prepare_new_message(&1, plural_forms))\n    }\n\n    stats = %{\n      new: length(po.messages),\n      exact_matches: 0,\n      fuzzy_matches: 0,\n      removed: 0,\n      marked_as_obsolete: 0\n    }\n\n    {po, stats}\n  end\n\n  @doc false\n  @spec prune_references(messages :: Messages.t(), gettext_config :: Keyword.t()) :: Messages.t()\n  def prune_references(%Messages{} = all, gettext_config) when is_list(gettext_config) do\n    cond do\n      # Empty out all references.\n      not Keyword.get(gettext_config, :write_reference_comments, true) ->\n        put_in(all, [Access.key!(:messages), Access.all(), Access.key(:references)], [])\n\n      # Remove lines from references and unique them.\n      not Keyword.get(gettext_config, :write_reference_line_numbers, true) ->\n        update_in(\n          all,\n          [Access.key!(:messages), Access.all(), Access.key(:references)],\n          &remove_line_and_unique_references/1\n        )\n\n      true ->\n        all\n    end\n  end\n\n  defp remove_line_and_unique_references(references) do\n    {unique_refs, _} =\n      references\n      |> update_in([Access.all(), Access.all()], fn\n        {file, _line} -> file\n        file -> file\n      end)\n      |> Enum.map_reduce(MapSet.new(), fn line, existing_references ->\n        unique_line = Enum.uniq(line) -- MapSet.to_list(existing_references)\n        {unique_line, MapSet.union(existing_references, MapSet.new(unique_line))}\n      end)\n\n    Enum.reject(unique_refs, &match?([], &1))\n  end\n\n  defp headers_for_new_po_file(locale, plural_forms_header) do\n    [\n      \"\",\n      ~s(Language: #{locale}\\n),\n      ~s(Plural-Forms: #{plural_forms_header}\\n)\n    ]\n  end\n\n  defp prepare_new_message(message, plural_forms) do\n    message\n    |> strip_double_hash_comments()\n    |> adjust_number_of_plural_forms(plural_forms)\n  end\n\n  defp strip_double_hash_comments(%{comments: comments} = message) do\n    %{message | comments: Enum.reject(comments, &match?(\"#\" <> _, &1))}\n  end\n\n  # TODO: simplify code here once we remove support for :plural_forms.\n  defp put_plural_forms_opt(opts, messages, locale) do\n    plural_mod = Application.get_env(:gettext, :plural_forms, Gettext.Plural)\n    default_nplurals = plural_mod.nplurals(Plural.plural_info(locale, messages, plural_mod))\n\n    opts = Keyword.put_new(opts, :plural_forms, default_nplurals)\n\n    Keyword.put_new_lazy(opts, :plural_forms_header, fn ->\n      requested_nplurals = Keyword.fetch!(opts, :plural_forms)\n\n      # If nplurals is overridden to a non-default value by the user the\n      # implementation will not be able to provide a correct header therefore\n      # the header is just set to `nplurals=#{n}` and it is up to the user to\n      # put a complete plural forms header themselves.\n      if requested_nplurals == default_nplurals do\n        Plural.plural_forms_header_impl(locale, messages, plural_mod)\n      else\n        \"nplurals=#{requested_nplurals}\"\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/gettext/missing_bindings_error.ex",
    "content": "defmodule Gettext.MissingBindingsError do\n  @moduledoc \"\"\"\n  An error message raised for missing bindings errors.\n  \"\"\"\n\n  @enforce_keys [:backend, :domain, :msgctxt, :locale, :msgid, :missing]\n  defexception [:backend, :domain, :msgctxt, :locale, :msgid, :missing]\n\n  @type t() :: %__MODULE__{}\n\n  @impl true\n  def message(%__MODULE__{\n        backend: backend,\n        domain: domain,\n        msgctxt: msgctxt,\n        locale: locale,\n        msgid: msgid,\n        missing: missing\n      }) do\n    \"missing Gettext bindings: #{inspect(missing)} (backend #{inspect(backend)}, \" <>\n      \"locale #{inspect(locale)}, domain #{inspect(domain)}, msgctxt #{inspect(msgctxt)}, \" <>\n      \"msgid #{inspect(msgid)})\"\n  end\nend\n"
  },
  {
    "path": "lib/gettext/plural.ex",
    "content": "defmodule Gettext.Plural do\n  @moduledoc \"\"\"\n  Behaviour and default implementation for finding plural forms in given\n  locales.\n\n  This module both defines the `Gettext.Plural` behaviour and provides a default\n  implementation for it.\n\n  ## Plural Forms\n\n  > For a given language, there is a grammatical rule on how to change words\n  > depending on the number qualifying the word. Different languages can have\n  > different rules.\n  [[source]](https://udn.realityripple.com/docs/Mozilla/Localization/Localization_and_Plurals)\n\n  Such grammatical rules define a number of **plural forms**. For example,\n  English has two plural forms: one for when there is just one element (the\n  *singular*) and another one for when there are zero or more than one elements\n  (the *plural*). There are languages which only have one plural form and there\n  are languages which have more than two.\n\n  In GNU Gettext (and in Gettext for Elixir), plural forms are represented by\n  increasing 0-indexed integers. For example, in English `0` means singular and\n  `1` means plural.\n\n  The goal of this module is to determine, given a locale:\n\n    * how many plural forms exist in that locale (`nplurals/1`);\n\n    * to what plural form a given number of elements belongs to in that locale\n      (`plural/2`).\n\n  ## Default Implementation\n\n  `Gettext.Plural` provides a default implementation of a plural module. Most\n  common languages used on Earth should be covered by this default implementation. If\n  custom pluralization rules are needed (for example, to add additional\n  languages) a different plural module can be specified when creating a Gettext\n  backend. For example, pluralization rules for the Elvish language could be\n  added as follows:\n\n      defmodule MyApp.Plural do\n        @behaviour Gettext.Plural\n\n        def nplurals(\"elv\"), do: 3\n\n        def plural(\"elv\", 0), do: 0\n        def plural(\"elv\", 1), do: 1\n        def plural(\"elv\", _), do: 2\n\n        # Fall back to Gettext.Plural\n        defdelegate nplurals(locale), to: Gettext.Plural\n        defdelegate plural(locale, n), to: Gettext.Plural\n      end\n\n  The mathematical expressions used in this module to determine the plural form\n  of a given number of elements are taken from [this\n  page](http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html#f2)\n  as well as from [Mozilla's guide on \"Localization and\n  plurals\"](https://udn.realityripple.com/docs/Mozilla/Localization/Localization_and_Plurals).\n\n  ## Changing Implementations\n\n  Once you have defined your custom plural forms module, you can use it\n  in two ways. You can set it for all Gettext backends in your\n  configuration:\n\n      # For example, in config/config.exs\n      config :gettext, :plural_forms, MyApp.Plural\n\n  or you can set it for each specific backend when you call `use Gettext`:\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend,\n          otp_app: :my_app,\n          plural_forms: MyApp.Plural\n      end\n\n  > #### Compile-time Configuration {: .warning}\n  >\n  > Set `:plural_forms` in your `config/config.exs` and\n  > not in `config/runtime.exs`, as Gettext reads this option when\n  > compiling your backends.\n\n  Task such as `mix gettext.merge` use the plural\n  backend configured under the `:gettext` application, so in general\n  the global configuration approach is preferred.\n\n  Some tasks also allow the number of plural forms to be given\n  explicitly, for example:\n\n      mix gettext.merge priv/gettext --locale=gsw_CH --plural-forms=2\n\n  ## Unknown Locales\n\n  Trying to call `Gettext.Plural` functions with unknown locales will result in\n  a `Gettext.Plural.UnknownLocaleError` exception.\n\n  ## Language and Territory\n\n  Often, a locale is composed as a language and territory pair, such as\n  `en_US`. The default implementation for `Gettext.Plural` handles `xx_YY` by\n  forwarding it to `xx` (except for *just Brazilian Portuguese*, `pt_BR`, which\n  is not forwarded to `pt` as pluralization rules differ slightly). We treat the\n  underscore as a separator according to\n  [ISO 15897](https://en.wikipedia.org/wiki/ISO/IEC_15897). Sometimes, a dash `-` is\n  used as a separator (for example [BCP47](https://en.wikipedia.org/wiki/IETF_language_tag)\n  locales use this as in `en-US`): this is *not forwarded* to `en` in the default\n  `Gettext.Plural` (and it will raise an `Gettext.Plural.UnknownLocaleError` exception\n  if there are no messages for `en-US`). We recommend defining a custom plural forms\n  module that replaces `-` with `_` if needed.\n\n  ## Examples\n\n  An example of the plural form of a given number of elements in the Polish\n  language:\n\n      iex> Gettext.Plural.plural(\"pl\", 1)\n      0\n      iex> Gettext.Plural.plural(\"pl\", 2)\n      1\n      iex> Gettext.Plural.plural(\"pl\", 5)\n      2\n      iex> Gettext.Plural.plural(\"pl\", 112)\n      2\n\n  As expected, `nplurals/1` returns the possible number of plural forms:\n\n      iex> Gettext.Plural.nplurals(\"pl\")\n      3\n\n  \"\"\"\n\n  alias Expo.Messages\n\n  # Types\n\n  @typedoc \"\"\"\n  A locale passed to `c:plural/2`.\n  \"\"\"\n  @typedoc since: \"0.22.0\"\n  @type locale() :: String.t()\n\n  @typedoc \"\"\"\n  The context passed to the optional `c:init/1` callback.\n\n  If `:plural_forms_header` is present, it contains the contents\n  of the `Plural-Forms` Gettext header.\n  \"\"\"\n  @typedoc since: \"0.22.0\"\n  @type pluralization_context() :: %{\n          required(:locale) => locale(),\n          optional(:plural_forms_header) => String.t()\n        }\n\n  @typedoc \"\"\"\n  The term that the optional `c:init/1` callback returns.\n  \"\"\"\n  @typedoc since: \"0.22.0\"\n  @type plural_info() :: term()\n\n  ## Behaviour definition\n\n  @doc \"\"\"\n  Should initialize the context for `c:nplurals/1` and `c:plural/2`.\n\n  This callback should perform all preparations for the provided locale, which\n  is part of the pluralization context (see `t:pluralization_context/0`). For\n  example, you can use this callback to parse the `Plural-Forms` header and\n  determine pluralization rules for the locale.\n\n  If defined, Gettext calls this callback *once* at compile time. If not defined,\n  the returned `plural_info` will be equals to the locale found in\n  `pluralization_context`.\n\n  ## Examples\n\n      defmodule MyApp.Plural do\n        @behaviour Gettext.Plural\n\n        @impl true\n        def init(%{locale: _locale, plural_forms_header: header}) do\n          {nplurals, rule} = parse_plural_forms_header(header)\n\n          # This is what other callbacks can use to determine the plural.\n          {nplurals, rule}\n        end\n\n        @impl true\n        def nplurals({_locale, nplurals, _rule}), do: nplurals\n\n        # ...\n      end\n\n  \"\"\"\n  @doc since: \"0.22.0\"\n  @callback init(pluralization_context()) :: plural_info()\n\n  @doc \"\"\"\n  Should return the number of possible plural forms in the given `locale`.\n  \"\"\"\n  @callback nplurals(plural_info()) :: pos_integer()\n\n  @doc \"\"\"\n  Should return the plural form in the given `locale` for the given `count` of\n  elements.\n  \"\"\"\n  @callback plural(plural_info(), count :: integer()) :: plural_form :: non_neg_integer()\n\n  @doc \"\"\"\n  Should return the value of the `Plural-Forms` header for the given `locale`,\n  if present.\n\n  If the value of the `Plural-Forms` header is unavailable for any reason, this\n  function should return `nil`.\n\n  This callback is optional. If it's not defined, the fallback returns:\n\n      \"nplurals={nplurals};\"\n\n  \"\"\"\n  @doc since: \"0.22.0\"\n  @callback plural_forms_header(locale()) :: String.t() | nil\n\n  @optional_callbacks init: 1, plural_forms_header: 1\n\n  defmodule UnknownLocaleError do\n    @moduledoc \"\"\"\n    Raised when a pluralized module doesn't know how to handle a locale.\n\n    ## Examples\n\n        raise Gettext.Plural.UnknownLocaleError, \"en-US\"\n\n    \"\"\"\n\n    defexception [:message]\n\n    def exception(locale) when is_binary(locale) do\n      message = \"\"\"\n      unknown locale #{inspect(locale)}. If this is a locale you need to handle,\n      consider using a custom pluralizer module instead of the default\n      Gettext.Plural. You can read more about this on the Gettext docs at\n      https://hexdocs.pm/gettext/Gettext.Plural.html\n      \"\"\"\n\n      %__MODULE__{message: message}\n    end\n  end\n\n  # Behaviour implementation.\n\n  # Default implementation of the init/1 callback, in case the user uses\n  # Gettext.Plural as their plural forms module.\n  @doc false\n  def init(context)\n\n  def init(%{locale: locale, plural_forms_header: plural_forms_header}) do\n    case Expo.PluralForms.parse(plural_forms_header) do\n      {:ok, plural_forms} ->\n        {locale, plural_forms}\n\n      {:error, _reason} ->\n        message_about_header =\n          case Expo.PluralForms.plural_form(locale) do\n            {:ok, plural_form} ->\n              \"\"\"\n\n              For the #{inspect(locale)} locale, you can use the following header:\n\n              #{Expo.PluralForms.to_string(plural_form)}\n              \"\"\"\n\n            :error ->\n              \"\"\n          end\n\n        # Fall back to parsing headers such as \"nplurals=3\", without the \"plural=...\" part.\n        # TODO: remove this in v0.24.0\n        with \"nplurals=\" <> rest <- String.trim(plural_forms_header),\n             {plural_forms, _rest} <- Integer.parse(rest) do\n          IO.warn(\"\"\"\n          Plural-Forms headers in the form \"nplurals=<int>\" (without the \"plural=<rule>\" part \\\n          following) are invalid and support for them will be removed in future Gettext \\\n          versions. Make sure to use a complete Plural-Forms header, which also specifies \\\n          the pluralization rules, or remove the Plural-Forms header completely. If you \\\n          do the latter, Gettext will use its built-in pluralization rules for the languages \\\n          it knows about (see Gettext.Plural).#{message_about_header}\\\n          \"\"\")\n\n          {locale, plural_forms}\n        else\n          _other -> locale\n        end\n    end\n  end\n\n  def init(%{locale: locale}), do: locale\n\n  # Number of plural forms.\n\n  @doc \"\"\"\n  Default implementation of the `c:nplurals/1` callback.\n  \"\"\"\n  def nplurals(locale)\n\n  # TODO: this is a fallback for headers such as \"nplurals=x\", without \"plural=...\".\n  # We should remove support for these at some point.\n  def nplurals({_locale, nplurals}) when is_integer(nplurals) do\n    nplurals\n  end\n\n  # If the nplurals was provided, we don't need to look at the locale.\n  def nplurals({_locale, plural_forms}) do\n    plural_forms.nplurals\n  end\n\n  def nplurals(locale) do\n    case Expo.PluralForms.plural_form(locale) do\n      {:ok, plural_form} -> plural_form.nplurals\n      :error -> recall_if_territory_or_raise(locale, &nplurals/1)\n    end\n  end\n\n  @doc \"\"\"\n  Default implementation of the `c:plural/2` callback.\n  \"\"\"\n  def plural(locale, count)\n\n  # TODO: this is a fallback for headers such as \"nplurals=x\", without \"plural=...\".\n  # We should remove support for these at some point.\n  def plural({locale, nplurals}, count) when is_integer(nplurals) do\n    plural(locale, count)\n  end\n\n  def plural({_locale, plural_form}, count) do\n    Expo.PluralForms.index(plural_form, count)\n  end\n\n  def plural(locale, count) do\n    case Expo.PluralForms.plural_form(locale) do\n      {:ok, plural_form} -> Expo.PluralForms.index(plural_form, count)\n      :error -> recall_if_territory_or_raise(locale, &plural(&1, count))\n    end\n  end\n\n  def plural_forms_header(locale) do\n    case Expo.PluralForms.plural_form(locale) do\n      {:ok, plural_form} -> Expo.PluralForms.to_string(plural_form)\n      :error -> recall_if_territory_or_raise(locale, &plural_forms_header(&1))\n    end\n  rescue\n    UnknownLocaleError -> nil\n  end\n\n  defp recall_if_territory_or_raise(locale, fun) do\n    case String.split(locale, \"_\", parts: 2, trim: true) do\n      [lang, _territory] -> fun.(lang)\n      _other -> raise UnknownLocaleError, locale\n    end\n  end\n\n  @doc false\n  def plural_info(locale, messages_struct, plural_mod) do\n    Code.ensure_compiled!(plural_mod)\n\n    if function_exported?(plural_mod, :init, 1) do\n      pluralization_context =\n        case IO.iodata_to_binary(Messages.get_header(messages_struct, \"Plural-Forms\")) do\n          \"\" -> %{locale: locale}\n          plural_forms -> %{locale: locale, plural_forms_header: plural_forms}\n        end\n\n      plural_mod.init(pluralization_context)\n    else\n      locale\n    end\n  end\n\n  @doc false\n  def plural_forms_header_impl(locale, messages_struct, plural_mod) do\n    Code.ensure_compiled!(plural_mod)\n\n    plural_forms_header =\n      if function_exported?(plural_mod, :plural_forms_header, 1) do\n        plural_mod.plural_forms_header(locale)\n      end\n\n    if plural_forms_header do\n      plural_forms_header\n    else\n      nplurals = plural_mod.nplurals(plural_info(locale, messages_struct, plural_mod))\n      \"nplurals=#{nplurals}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/gettext/plural_form_error.ex",
    "content": "defmodule Gettext.PluralFormError do\n  @moduledoc \"\"\"\n  An generic error for when a plural form is missing for a given locale.\n  \"\"\"\n\n  @enforce_keys [:form, :locale, :file, :line]\n  defexception [:form, :locale, :file, :line]\n\n  @type t() :: %__MODULE__{\n          form: non_neg_integer(),\n          locale: String.t(),\n          file: String.t(),\n          line: pos_integer()\n        }\n\n  @impl true\n  def message(%__MODULE__{form: form, locale: locale, file: file, line: line}) do\n    \"plural form #{form} is required for locale #{inspect(locale)} \" <>\n      \"but is missing for message compiled from #{file}:#{line}\"\n  end\nend\n"
  },
  {
    "path": "lib/gettext.ex",
    "content": "defmodule Gettext do\n  @moduledoc ~S\"\"\"\n  The `Gettext` module provides a\n  [gettext](https://www.gnu.org/software/gettext/)-based API for working with\n  internationalized applications.\n\n  ## Basic Overview\n\n  When you use Gettext, you replace hardcoded user-facing text like this:\n\n      \"Hello world\"\n\n  with calls like this:\n\n      gettext(\"Hello world\")\n\n  Here, the string `\"Hello world\"` serves two purposes:\n\n    1. It's displayed by default (if no translation is specified in the current\n       language). This means that, at the very least, switching from a hardcoded\n       string to a Gettext call is harmless.\n\n    2. It serves as the **message ID** to which translations will be mapped.\n\n  An example translation workflow is as follows.\n\n  First, call `mix gettext.extract` to extract `gettext()` calls to `.pot`\n  ([Portable Object Template](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html))\n  files, which are the base for all translations. These files are *templates*, which\n  means they only contain message IDs, and not actual translated strings. POT files have\n  entries like this:\n\n      #: lib/my_app_web/live/hello_live.html.heex:2\n      #, elixir-autogen, elixir-format\n      msgid \"Hello world\"\n      msgstr \"\"\n\n  Then, call `mix gettext.merge priv/gettext` to update all\n  locale-specific `.po` (Portable Object) files so that they include this message ID.\n  Entries in PO files contain translations for their specific locale. For example,\n  in a PO file for Italian, the entry above would look like this:\n\n      #: lib/my_app_web/live/hello_live.html.heex:2\n      #, elixir-autogen, elixir-format\n      msgid \"Hello world\"\n      msgstr \"Ciao mondo\"\n\n  The English string is the `msgid` which is used to look up the\n  correct Italian string.\n  That's handy, because unlike a generic key like `site.greeting` (as some\n  translations systems use), the message ID tells exactly what needs to be\n  translated. This is easier to work with for translators, for example.\n\n  But it raises a question: what if you change the original English string in the code?\n  Does that break all translations, requiring manual edits everywhere? Not necessarily.\n  After you run `mix gettext.extract` again, the next `mix gettext.merge` can\n  do **fuzzy matching**.\n  So, if you change `\"Hello world\"` to `\"Hello world!\"`, Gettext will see that the new\n  message ID is similar to an existing `msgid`, and will do two things:\n\n    1. It will update the `msgid` in all `.po` files to match the new text.\n\n    2. It will mark those entries as \"fuzzy\"; this hints that a (probably human)\n       translator should check whether the Italian translation of this string needs\n       an update.\n\n  The resulting change in the `.po` file is this (note the \"fuzzy\" annotation):\n\n      #: lib/myapp_web/live/hello_live.html.heex:2\n      #, elixir-autogen, elixir-format, fuzzy\n      msgid \"Hello world!\"\n      msgstr \"Ciao mondo\"\n\n  This \"fuzzy matching\" behavior can be configured or disabled, but its\n  existence makes updating translations to match changes in the base text easier.\n\n  The rest of the documentation will cover the Gettext API in detail.\n\n  ## Gettext API\n\n  To use Gettext, you will need a **backend module** which stores and retrieves\n  translations from PO files. You can create such a module by using `Gettext.Backend`:\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n  Now, you can import all the necessary translation macros (defined in `Gettext.Macros`)\n  into any module by using `Gettext`:\n\n      defmodule MyApp.SomeModule do\n        use Gettext, backend: MyApp.Gettext\n\n        def showcase_gettext do\n          # Simple message\n          gettext(\"Hello world\")\n\n          # Plural message\n          ngettext(\n            \"Here is the string to translate\",\n            \"Here are the strings to translate\",\n            3\n          )\n\n          # Domain-based message\n          dgettext(\"errors\", \"Here is the error message to translate\")\n\n          # Context-based message\n          pgettext(\"email\", \"Email text to translate\")\n        end\n      end\n\n  The arguments for the Gettext macros and their order can be derived from\n  their names. For example, for [`dpgettext/4`](`Gettext.Macros.dpgettext/4`)\n  the arguments are: `domain`, `context`, `msgid`, `bindings` (default to `%{}`).\n\n  Messages are looked up from `.po` files. In the following sections we will\n  explore exactly what are those files before we explore the \"Gettext API\" in\n  detail.\n\n  > #### Recent Updates {: .info}\n  >\n  > Before v0.26.0 of this library, the workflow described in this section\n  > was slightly different. Check out [the\n  > changelog](https://github.com/elixir-gettext/gettext/blob/main/CHANGELOG.md) for more\n  > details, but the gist is that `use Gettext` used to define macros in the calling module.\n  > This created heavy compile-time dependencies which would cause slow recompilation\n  > in larger applications.\n\n  ## Messages\n\n  Messages are stored inside PO (Portable Object) files, with a `.po`\n  extension. For example, this is a snippet from a PO file:\n\n      # This is a comment\n      msgid \"Hello world!\"\n      msgstr \"Ciao mondo!\"\n\n  PO files containing messages for an application must be stored in a\n  directory (by default it's `priv/gettext`) that has the following structure:\n\n      gettext directory\n      └─ locale\n         └─ LC_MESSAGES\n            ├─ domain_1.po\n            ├─ domain_2.po\n            └─ domain_3.po\n\n  Here, `locale` is the locale of the messages (for example, `en_US`),\n  `LC_MESSAGES` is a fixed directory, and `domain_i.po` are PO files containing\n  domain-scoped messages. For more information on domains, check out the\n  \"Domains\" section below.\n\n  A concrete example of such a directory structure could look like this:\n\n      priv/gettext\n      └─ en_US\n      |  └─ LC_MESSAGES\n      |     ├─ default.po\n      |     └─ errors.po\n      └─ it\n         └─ LC_MESSAGES\n            ├─ default.po\n            └─ errors.po\n\n  By default, Gettext expects messages to be stored under the `priv/gettext`\n  directory of an application. This behaviour can be changed by specifying a\n  `:priv` option when using `Gettext`:\n\n      # Look for messages in my_app/priv/messages instead of\n      # my_app/priv/gettext\n      use Gettext.Backend,\n        otp_app: :my_app,\n        priv: \"priv/messages\"\n\n  The messages directory specified by the `:priv` option should be a directory\n  inside `priv/`, otherwise some things won't work as expected.\n\n  ## Locale\n\n  At runtime, all gettext-related functions and macros that do not explicitly\n  take a locale as an argument read the locale from the backend and fall back\n  to Gettext's default locale.\n\n  `Gettext.put_locale/1` can be used to change the locale of all backends for\n  the current Elixir process. That's the preferred mechanism for setting the\n  locale at runtime. `Gettext.put_locale/2` can be used when you want to set the\n  locale of one specific Gettext backend without affecting other Gettext\n  backends.\n\n  Similarly, `Gettext.get_locale/0` gets the locale for all backends in the\n  current process. `Gettext.get_locale/1` gets the locale of a specific backend\n  for the current process. Check their documentation for more information.\n\n  Locales are expressed as strings (like `\"en\"` or `\"fr\"`); they can be\n  arbitrary strings as long as they match a directory name. As mentioned above,\n  the locale is stored **per-process** (in the process dictionary): this means\n  that the locale must be set in every new process in order to have the right\n  locale available for that process. Pay attention to this behaviour, since not\n  setting the locale *will not* result in any errors when `Gettext.get_locale/0`\n  or `Gettext.get_locale/1` are called; the default locale will be\n  returned instead.\n\n  To decide which locale to use, each gettext-related function in a given\n  backend follows these steps:\n\n    * if there is a backend-specific locale for the given backend for this\n      process (see `put_locale/2`), use that, otherwise\n    * if there is a global locale for this process (see `put_locale/1`), use\n      that, otherwise\n    * if there is a backend-specific default locale in the configuration for\n      that backend's `:otp_app` (see the \"Default locale\" section below), use\n      that, otherwise\n    * use the default global Gettext locale (see the \"Default locale\" section\n      below)\n\n  ### Default locale\n\n  The global Gettext default locale can be configured through the\n  `:default_locale` key of the `:gettext` application:\n\n      config :gettext, :default_locale, \"fr\"\n\n  By default the global locale is `\"en\"`. See also `get_locale/0` and\n  `put_locale/1`.\n\n  If for some reason a backend requires a different `:default_locale`\n  than all other backends, you can set the `:default_locale` inside the\n  backend configuration, but this approach is generally discouraged as\n  it makes it hard to track which locale each backend is using:\n\n      config :my_app, MyApp.Gettext, default_locale: \"fr\"\n\n  ## Gettext API\n\n  There are two ways to use Gettext:\n\n    * using macros from your own Gettext module, like `MyApp.Gettext`\n    * using functions from the `Gettext` module\n\n  These two approaches are different and each one has its own use case.\n\n  ### Using macros\n\n  Each module that calls `use Gettext.Backend` is usually referred to as a \"Gettext\n  backend\", as it implements the `Gettext.Backend` behaviour. When a module then calls\n  `use Gettext, backend: MyApp.Gettext`, all the macros defined in `Gettext.Macros`\n  are imported into that module, such as:\n\n    * [`gettext/2`](`Gettext.Macros.gettext/2`)\n    * [`dgettext/3`](`Gettext.Macros.dgettext/3`)\n    * [`pgettext/3`](`Gettext.Macros.pgettext/3`)\n\n  Using macros is preferred as Gettext is able to automatically sync the\n  messages in your code with PO files. This, however, imposes a constraint:\n  arguments passed to any of these macros have to be strings **at compile\n  time**. This means that they have to be string literals or something that\n  expands to a string literal at compile time (for example, a module attribute like\n  `@my_string \"foo\"`).\n\n  These are all valid uses of the Gettext macros:\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n\n      use Gettext, backend: MyApp.Gettext\n\n      gettext(\"Hello world\")\n      #=> \"Ciao mondo\"\n\n      @msgid \"Hello world\"\n      gettext(@msgid)\n      #=> \"Ciao mondo\"\n\n  The `*gettext` macros raise an `ArgumentError` exception if they receive a\n  `domain`, `msgctxt`, `msgid`, or `msgid_plural` that doesn't expand to a string\n  *at compile time*:\n\n      msgid = \"Hello world\"\n      gettext(msgid)\n      #=> ** (ArgumentError) msgid must be a string literal\n\n  Using compile-time strings isn't always possible. For this reason,\n  the `Gettext` module provides a set of functions as well.\n\n  ### Using functions\n\n  If compile-time strings cannot be used, the solution is to use the functions\n  in the `Gettext` module instead of the macros described above. These functions\n  perfectly mirror the macro API, but they all expect a Gettext backend module\n  as the first argument.\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.put_locale(MyApp.Gettext, \"pt_BR\")\n\n      msgid = \"Hello world\"\n      Gettext.gettext(MyApp.Gettext, msgid)\n      #=> \"Olá mundo\"\n\n  While using functions from the `Gettext` module yields the same results as\n  using macros (with the added benefit of dynamic arguments), all the\n  compile-time features mentioned in the previous section are lost.\n\n  ## Domains\n\n  The [`dgettext`](`Gettext.Macros.dgettext/3`) and [`dngettext`](`Gettext.Macros.dngettext/5`)\n  macros (and their function counterparts) also accept a *domain* as one\n  of the arguments. The domain of a message is determined by the name of the\n  PO file that contains that message. For example, the domain of\n  messages in the `it/LC_MESSAGES/errors.po` file is `\"errors\"`, so those\n  messages would need to be retrieved with `dgettext` or `dngettext`:\n\n      dgettext(\"errors\", \"Error!\")\n      #=> \"Errore!\"\n\n  When backend `gettext`, `ngettext`, or `pgettext` are used, the backend's\n  default domain is used (which defaults to \"default\"). The `Gettext`\n  functions accepting a backend (`gettext/3`, `ngettext/5`, and `pgettext/4`)\n  _always_ use a domain of \"default\".\n\n  ### Default Domain\n\n  Each backend can be configured with a specific `:default_domain`\n  that replaces `\"default\"` in `gettext/2`, `pgettext/3`, and `ngettext/4`\n  for that backend.\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend,\n          otp_app: :my_app,\n          default_domain: \"messages\"\n      end\n\n      config :my_app, MyApp.Gettext, default_domain: \"messages\"\n\n  ## Contexts\n\n  The GNU Gettext implementation supports\n  [*contexts*](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html),\n  which are a way to contextualize messages. For example, in English, the\n  word \"file\" could be used both as a noun as well as a verb. Contexts can be used to\n  solve similar problems: you could have a `imperative_verbs` context and a\n  `nouns` context as to avoid ambiguity. The functions that handle contexts\n  have a `p` in their name (to match the GNU Gettext API), and are `pgettext`,\n  `dpgettext`, `pngettext`, and `dpngettext`. The \"p\" stands for \"particular\".\n\n  ## Interpolation\n\n  All `*gettext` functions and macros provided by Gettext support interpolation.\n  Interpolation keys can be placed in `msgid`s or `msgid_plural`s with by\n  enclosing them in `%{` and `}`, like this:\n\n      \"This is an %{interpolated} string\"\n\n  Interpolation bindings can be passed as an argument to all of the `*gettext`\n  functions/macros. For example, given the following PO file for the `\"it\"`\n  locale:\n\n      msgid \"Hello, %{name}!\"\n      msgstr \"Ciao, %{name}!\"\n\n  interpolation can be done like follows:\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n      gettext(\"Hello, %{name}!\", name: \"Meg\")\n      #=> \"Ciao, Meg!\"\n\n  Interpolation keys that are in a string but not in the provided bindings\n  result in an exception:\n\n      gettext(\"Hello, %{name}!\")\n      #=> ** (Gettext.MissingBindingsError) ...\n\n  Keys that are in the interpolation bindings but that don't occur in the string\n  are ignored. Interpolations in Gettext are often expanded at compile time,\n  ensuring a low performance cost when running them at runtime.\n\n  ## Pluralization\n\n  Pluralization in Gettext for Elixir works very similar to how pluralization\n  works in GNU Gettext. The `*ngettext` functions/macros accept a `msgid`, a\n  `msgid_plural`, and a count of elements; the right message is chosen based\n  on the **pluralization rule** for the given locale.\n\n  For example, given the following snippet of PO file for the `\"it\"` locale:\n\n      msgid \"One error\"\n      msgid_plural \"%{count} errors\"\n      msgstr[0] \"Un errore\"\n      msgstr[1] \"%{count} errori\"\n\n  the `ngettext` macro can be used like this:\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n      ngettext(\"One error\", \"%{count} errors\", 3)\n      #=> \"3 errori\"\n\n  The `%{count}` interpolation key is a special key since it gets replaced by\n  the number of elements argument passed to `*ngettext`, like if the `count: 3`\n  key-value pair were in the interpolation bindings. Hence, never pass the\n  `count` key in the bindings:\n\n      # `count: 4` is ignored here\n      ngettext(\"One error\", \"%{count} errors\", 3, count: 4)\n      #=> \"3 errori\"\n\n  You can specify a \"pluralizer\" module via the `:plural_forms` option in the\n  configuration for each Gettext backend.\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend,\n          otp_app: :my_app,\n          plural_forms: MyApp.PluralForms\n      end\n\n  To learn more about pluralization rules, plural forms and what they mean to\n  Gettext check the documentation for `Gettext.Plural`.\n\n  ## Missing messages\n\n  When a message is missing in the specified locale (both with functions and\n  with macros), the argument is returned:\n\n    * in case of calls to `gettext`/`dgettext`/`pgettext`/`dpgettext`, the `msgid` argument is returned\n      as is;\n    * in case of calls to `ngettext`/`dngettext`/`pngettext`/`dpngettext`, the `msgid` argument is\n      returned in case of a singular value and the `msgid_plural` is returned in\n      case of a plural value (following the English pluralization rule).\n\n  For example:\n\n      Gettext.put_locale(MyApp.Gettext, \"foo\")\n      gettext(\"Hey there\")\n      #=> \"Hey there\"\n      ngettext(\"One error\", \"%{count} errors\", 3)\n      #=> \"3 errors\"\n\n  ### Empty messages\n\n  When a `msgstr` is empty (`\"\"`), the message is considered missing and the\n  behaviour described above for missing message is applied. A plural\n  message is considered to have an empty `msgstr` if at least one\n  message in the `msgstr` is empty.\n\n  ## Compile-time features\n\n  As mentioned above, using the Gettext macros (as opposed to functions) allows\n  Gettext to operate on those messages *at compile-time*. This can be used\n  to extract messages from the source code into POT (Portable Object Template)\n  files automatically (instead of having to manually add messages to POT files\n  when they're added to the source code). `mix gettext.extract` does exactly\n  this: whenever there are new messages in the source code, running\n  this task syncs the existing POT files with the changed code base.\n  Read the documentation for `mix gettext.extract` for more information\n  on the extraction process.\n\n  POT files are just *template* files and the messages in them do not\n  actually contain translated strings. A POT file looks like this:\n\n      # The msgstr is empty\n      msgid \"hello, world\"\n      msgstr \"\"\n\n  Whenever a POT file changes, it's likely that developers (or translators) will\n  want to update the corresponding PO files for each locale. To do that, gettext\n  provides the `gettext.merge` Mix task. For example, running:\n\n      mix gettext.merge priv/gettext --locale pt_BR\n\n  will update all the PO files in `priv/gettext/pt_BR/LC_MESSAGES` with the new\n  version of the POT files in `priv/gettext`. Read more about the merging\n  process in the documentation for `mix gettext.merge`.\n\n  ## Configuration\n\n  ### `:gettext` configuration\n\n  The `:gettext` application supports the following configuration options:\n\n    * `:default_locale` - a string which specifies the default global Gettext\n      locale to use for all backends. See the \"Locale\" section for more\n      information on backend-specific, global, and default locales.\n\n  ### Backend configuration\n\n  A **Gettext backend** supports some options to be configured. These options\n  can be configured in two ways: either by passing them to `use Gettext` (hence\n  at compile time):\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, options\n      end\n\n  or by using Mix configuration, configuring the key corresponding to the\n  backend in the configuration for your application:\n\n      # For example, in config/config.exs\n      config :my_app, MyApp.Gettext, options\n\n  The `:otp_app` option (an atom representing an OTP application) has\n  to always be present and has to be passed to `use Gettext` because it's used\n  to determine the application to read the configuration of (`:my_app` in the\n  example above); for this reason, `:otp_app` can't be configured via the Mix\n  configuration. This option is also used to determine the application's\n  directory where to search messages in.\n\n  The following is a comprehensive list of supported options:\n\n    * `:priv` - a string representing a directory where messages will be\n      searched. The directory is relative to the directory of the application\n      specified by the `:otp_app` option. It is recommended to always have\n      this directory inside `\"priv\"`, otherwise some features won't work as expected.\n      By default it's `\"priv/gettext\"`.\n\n    * `:plural_forms` - a module which will act as a \"pluralizer\". For more\n      information, look at the documentation for `Gettext.Plural`.\n\n    * `:default_locale` - a string which specifies the default locale to use for\n      the given backend.\n\n    * `:split_module_by` - instead of bundling all locales into a single\n      module, this option makes Gettext build internal modules per locale,\n      per domain, or both. This reduces compilation times and beam file sizes\n      for large projects. For example: `split_module_by: [:locale, :domain]`.\n\n    * `:split_module_compilation` - control if compilation of split modules\n      should happen in `:parallel` (the default) or `:serial`.\n\n    * `:allowed_locales` - a list of locales to bundle in the backend.\n      Defaults to all the locales discovered in the `:priv` directory.\n      This option can be useful in development to reduce compile-time\n      by compiling only a subset of all available locales.\n\n    * `:interpolation` - the name of a module that implements the\n      `Gettext.Interpolation` behaviour. Default: `Gettext.Interpolation.Default`\n\n  ### Mix tasks configuration\n\n  You can configure Gettext Mix tasks under the `:gettext` key in the\n  configuration returned by `project/0` in `mix.exs`:\n\n      def project() do\n        [app: :my_app,\n         # ...\n         gettext: [...]]\n      end\n\n  The following is a list of the supported configuration options:\n\n    * `:fuzzy_threshold` - the default threshold for the Jaro distance measuring\n      the similarity of messages. Look at the documentation for the `mix\n      gettext.merge` task (`Mix.Tasks.Gettext.Merge`) for more information on\n      fuzzy messages.\n\n    * `:excluded_refs_from_purging` - a regex that is matched against message\n      references. Gettext will preserve all messages in all POT files that\n      have a matching reference. You can use this pattern to prevent Gettext from\n      removing messages that you have extracted using another tool.\n\n    * `:custom_flags_to_keep` - a list of custom flags that will be kept for\n      existing messages during a merge. Gettext always keeps the `fuzzy` flag.\n      If you want to keep the `elixir-format` flag, which is also commonly\n      used by Gettext, add it to this list. Available since v0.23.0.\n\n    * `:write_reference_comments` - a boolean that specifies whether reference\n      comments should be written when outputting PO(T) files. If this is `false`,\n      reference comments will not be written when extracting messages or merging\n      messages, and the ones already found in files will be discarded.\n\n    * `:write_reference_line_numbers` - a boolean that specifies whether file\n      reference comments include line numbers when outputting PO(T) files.\n      Defaults to `true`.\n\n    * `:sort_by_msgid` - modifies the sorting behavior. Can be either `nil` (the default),\n      `:case_sensitive`, or `:case_insensitive`.\n      By default or if `nil`, the order of existing messages in a POT file is kept and new\n      messages are appended to the file. If `:sort_by_msgid` is set to `:case_sensitive`,\n      existing and new messages will be mixed and sorted alphabetically by msgid.\n      If set to `:case_insensitive`, the same applies but the sorting is case insensitive.\n      *Note*: this option also supports `true` and `false` for backwards compatibility,\n      but these values are deprecated as of v0.21.0.\n\n    * `:on_obsolete` - controls what happens when obsolete messages are found.\n      If `:mark_as_obsolete`, messages are kept and marked as obsolete.\n      If `:delete`, obsolete messages are deleted. Defaults to `:delete`.\n\n    * `:store_previous_message_on_fuzzy_match` - a boolean that controls\n      whether to store the previous message text in case of a fuzzy match.\n      Defaults to `false`.\n\n  \"\"\"\n\n  alias Gettext.MissingBindingsError\n\n  @type locale :: binary\n  @type backend :: module\n  @type bindings :: map() | Keyword.t()\n\n  @typedoc \"\"\"\n  A Gettext domain.\n\n  See [*Domains*](#module-domains) in the module documentation for more information.\n  \"\"\"\n  @typedoc since: \"0.26.0\"\n  @type domain() :: :default | binary()\n\n  defguardp is_domain(domain) when domain == :default or is_binary(domain)\n\n  @doc false\n  defmacro __using__(opts) do\n    opts =\n      if Macro.quoted_literal?(opts) do\n        Macro.prewalk(opts, &expand_alias(&1, __CALLER__))\n      else\n        opts\n      end\n\n    case Keyword.keyword?(opts) && Keyword.fetch(opts, :backend) do\n      {:ok, backend} ->\n        case Macro.expand(backend, __CALLER__) do\n          backend when is_atom(backend) and backend not in [nil, false, true] ->\n            # We need to store the module backend at expansion time because of extraction\n            Module.put_attribute(__CALLER__.module, :__gettext_backend__, backend)\n\n            quote do\n              import Gettext.Macros\n            end\n\n          _ ->\n            raise ArgumentError,\n                  \"the :backend option on \\\"use Gettext\\\" expects the backend \" <>\n                    \"to be a literal atom/alias/module, got: #{Macro.to_string(backend)}\"\n        end\n\n      _other ->\n        # TODO: remove this once we stop supporting the old way of defining backends.\n        otp_app = Keyword.get(opts, :otp_app, :my_app)\n\n        IO.warn(\n          \"\"\"\n          Defining a Gettext backend by calling:\n\n              use Gettext, otp_app: #{inspect(otp_app)}\n\n          is deprecated. To define a backend, call:\n\n              use Gettext.Backend, otp_app: #{inspect(otp_app)}\n\n          Then, replace importing your backend:\n\n              import #{inspect(__CALLER__.module)}\n\n          with calling this in your module:\n\n              use Gettext, backend: #{inspect(__CALLER__.module)}\n          \"\"\",\n          Macro.Env.stacktrace(__CALLER__)\n        )\n\n        quote do\n          use Gettext.Backend, unquote(opts)\n          @before_compile {Gettext.Compiler, :generate_macros}\n        end\n    end\n  end\n\n  defp expand_alias({:__aliases__, _, _} = als, env) do\n    Macro.expand(als, %{env | function: {:__gettext__, 1}})\n  end\n\n  defp expand_alias(other, _env) do\n    other\n  end\n\n  @doc \"\"\"\n  Gets the global Gettext locale for the current process.\n\n  This function returns the value of the global Gettext locale for the current\n  process. This global locale is shared between all Gettext backends; if you\n  want backend-specific locales, see `get_locale/1` and `put_locale/2`. If the\n  global Gettext locale is not set, this function returns the default global\n  locale (configurable in the configuration for the `:gettext` application, see\n  the module documentation for more information).\n\n  ## Examples\n\n      Gettext.get_locale()\n      #=> \"en\"\n\n  \"\"\"\n  @doc section: :locale\n  @spec get_locale() :: locale\n  def get_locale() do\n    with nil <- Process.get(Gettext) do\n      # If this is not set by the user, it's still set in mix.exs (to \"en\").\n      Application.fetch_env!(:gettext, :default_locale)\n    end\n  end\n\n  @doc \"\"\"\n  Sets the **global** Gettext locale for the current process.\n\n  The locale is stored in the process dictionary. `locale` must be a string; if\n  it's not, an `ArgumentError` exception is raised.\n\n  The return value is the previous value of the current\n  process's locale.\n\n  > #### Unknown Locales {: .warning}\n  >\n  > Since this function sets the *global* locale, it cannot check whether that\n  > local is supported against a particular backend. For that, use `put_locale/2`\n  > or `put_locale!/2`.\n\n  ## Examples\n\n      Gettext.put_locale(\"pt_BR\")\n      #=> nil\n      Gettext.get_locale()\n      #=> \"pt_BR\"\n\n  \"\"\"\n  @doc section: :locale\n  @spec put_locale(locale) :: locale | nil\n  def put_locale(locale) when is_binary(locale), do: Process.put(Gettext, locale)\n\n  def put_locale(locale),\n    do: raise(ArgumentError, \"put_locale/1 only accepts binary locales, got: #{inspect(locale)}\")\n\n  @doc \"\"\"\n  Gets the locale for the current process and the given backend.\n\n  This function returns the value of the locale for the current process and the\n  given `backend`. If there is no locale for the current process and the given\n  backend, then either the global Gettext locale (if set), or the default locale\n  for the given backend, or the global default locale is returned. See the\n  \"Locale\" section in the module documentation for more information.\n\n  ## Examples\n\n      Gettext.get_locale(MyApp.Gettext)\n      #=> \"en\"\n\n  \"\"\"\n  @doc section: :locale\n  @spec get_locale(backend) :: locale\n  def get_locale(backend) do\n    with nil <- Process.get(backend),\n         nil <- Process.get(Gettext) do\n      backend.__gettext__(:default_locale)\n    end\n  end\n\n  @doc \"\"\"\n  Sets the locale for the current process and the given `backend`.\n\n  The locale is stored in the process dictionary. `locale` must be a string; if\n  it's not, an `ArgumentError` exception is raised.\n\n  The return value is the previous value of the current\n  process's locale.\n\n  ## Examples\n\n      Gettext.put_locale(MyApp.Gettext, \"pt_BR\")\n      #=> nil\n      Gettext.get_locale(MyApp.Gettext)\n      #=> \"pt_BR\"\n\n  The current process's locale will change even if the passed `locale` is not\n  supported. If you think this can cause an issue consider using `known_locales/1`\n  to handle unsupported locales:\n\n      # Handle unsupported locales based on your requirements\n      defp handle_locale(locale, true, backend), do: {:ok, Process.put(backend, locale)}\n      defp handle_locale(_locale, false, backend), do: {:error, :unsupported_locale}\n\n      # In your main function\n      is_in_allowed_locale = locale in known_locales(backend)\n      handle_locale(locale, is_in_allowed_locale, backend)\n\n  Alternatively, use `put_locale!/2` which raises if the locale is not supported.\n  \"\"\"\n  @doc section: :locale\n  @spec put_locale(backend, locale) :: locale | nil\n  def put_locale(backend, locale) when is_binary(locale), do: Process.put(backend, locale)\n\n  def put_locale(_backend, locale),\n    do: raise(ArgumentError, \"put_locale/2 only accepts binary locales, got: #{inspect(locale)}\")\n\n  @doc \"\"\"\n  Like `put_locale/2`, but it raises an error if the passed locale doesn't exist in the known locales.\n\n    ## Examples\n\n      Gettext.put_locale(MyApp.Gettext, \"pt_BR\")\n      #=> nil\n      Gettext.get_locale(MyApp.Gettext)\n      #=> \"pt_BR\"\n\n  \"\"\"\n  @doc section: :locale\n  @doc since: \"1.0.0\"\n  @spec put_locale!(backend, locale) :: locale | nil\n  def put_locale!(backend, locale) when is_binary(locale) do\n    cond do\n      not is_binary(locale) ->\n        raise ArgumentError, \"put_locale/2 only accepts binary locales, got: #{inspect(locale)}\"\n\n      locale in known_locales(backend) ->\n        put_locale(backend, locale)\n\n      true ->\n        raise ArgumentError, \"put_locale!/2 only support known locales, got: #{inspect(locale)}\"\n    end\n  end\n\n  @doc \"\"\"\n  Returns the message of the given string with a given context in the given domain.\n\n  The string is translated by the `backend` module.\n\n  The translated string is interpolated based on the `bindings` argument. For\n  more information on how interpolation works, refer to the documentation of the\n  `Gettext` module.\n\n  If the message for the given `msgid` is not found, the `msgid`\n  (interpolated if necessary) is returned.\n\n  ## Examples\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n\n      Gettext.dpgettext(MyApp.Gettext, \"errors\", \"user error\", \"Invalid\")\n      #=> \"Non valido\"\n\n      Gettext.dgettext(MyApp.Gettext, \"errors\", \"signup form\", \"%{name} is not a valid name\", name: \"Meg\")\n      #=> \"Meg non è un nome valido\"\n\n  \"\"\"\n  @doc section: :translation\n  @spec dpgettext(module, domain, binary | nil, binary, bindings) :: binary\n  def dpgettext(backend, domain, msgctxt, msgid, bindings \\\\ %{})\n\n  def dpgettext(backend, domain, msgctxt, msgid, bindings) when is_list(bindings) do\n    dpgettext(backend, domain, msgctxt, msgid, Map.new(bindings))\n  end\n\n  def dpgettext(backend, domain, msgctxt, msgid, bindings)\n      when is_atom(backend) and is_domain(domain) and is_binary(msgid) and is_map(bindings) do\n    domain = domain_or_default(backend, domain)\n    locale = get_locale(backend)\n    result = backend.lgettext(locale, domain, msgctxt, msgid, bindings)\n    handle_backend_result(result, backend, locale, domain, msgctxt, msgid)\n  end\n\n  @doc \"\"\"\n  Returns the message of the given string in the given domain.\n\n  The string is translated by the `backend` module.\n\n  The translated string is interpolated based on the `bindings` argument. For\n  more information on how interpolation works, refer to the documentation of the\n  `Gettext` module.\n\n  If the message for the given `msgid` is not found, the `msgid`\n  (interpolated if necessary) is returned.\n\n  ## Examples\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n\n      Gettext.dgettext(MyApp.Gettext, \"errors\", \"Invalid\")\n      #=> \"Non valido\"\n\n      Gettext.dgettext(MyApp.Gettext, \"errors\", \"%{name} is not a valid name\", name: \"Meg\")\n      #=> \"Meg non è un nome valido\"\n\n      Gettext.dgettext(MyApp.Gettext, \"alerts\", \"nonexisting\")\n      #=> \"nonexisting\"\n\n  \"\"\"\n  @doc section: :translation\n  @spec dgettext(module, domain, binary, bindings) :: binary\n  def dgettext(backend, domain, msgid, bindings \\\\ %{}) do\n    dpgettext(backend, domain, nil, msgid, bindings)\n  end\n\n  @doc \"\"\"\n  Returns the message of the given string with the given context\n\n  The string is translated by the `backend` module.\n\n  The translated string is interpolated based on the `bindings` argument. For\n  more information on how interpolation works, refer to the documentation of the\n  `Gettext` module.\n\n  If the message for the given `msgid` is not found, the `msgid`\n  (interpolated if necessary) is returned.\n\n  ## Examples\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.put_locale(MyApp.Gettext, \"it\")\n\n      Gettext.pgettext(MyApp.Gettext, \"user-interface\", \"Invalid\")\n      #=> \"Non valido\"\n\n      Gettext.pgettext(MyApp.Gettext, \"user-interface\", \"%{name} is not a valid name\", name: \"Meg\")\n      #=> \"Meg non è un nome valido\"\n\n      Gettext.pgettext(MyApp.Gettext, \"alerts-users\", \"nonexisting\")\n      #=> \"nonexisting\"\n  \"\"\"\n  @doc section: :translation\n  @spec pgettext(module, binary, binary, bindings) :: binary\n  def pgettext(backend, msgctxt, msgid, bindings \\\\ %{}) do\n    dpgettext(backend, \"default\", msgctxt, msgid, bindings)\n  end\n\n  @doc \"\"\"\n  Returns the message of the given string in the `\"default\"` domain.\n\n  Works exactly like:\n\n      Gettext.dgettext(backend, \"default\", msgid, bindings)\n\n  \"\"\"\n  @doc section: :translation\n  @spec gettext(module, binary, bindings) :: binary\n  def gettext(backend, msgid, bindings \\\\ %{}) do\n    dgettext(backend, \"default\", msgid, bindings)\n  end\n\n  @doc \"\"\"\n  Returns the pluralized message of the given string with a given context in the given domain.\n\n  The string is translated and pluralized by the `backend` module.\n\n  The translated string is interpolated based on the `bindings` argument. For\n  more information on how interpolation works, refer to the documentation of the\n  `Gettext` module.\n\n  If the message for the given `msgid` and `msgid_plural` is not found, the\n  `msgid` or `msgid_plural` (based on `n` being singular or plural) is returned\n  (interpolated if necessary).\n\n  ## Examples\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.dpngettext(MyApp.Gettext, \"errors\", \"user error\", \"Error\", \"%{count} errors\", 3)\n      #=> \"3 errori\"\n      Gettext.dpngettext(MyApp.Gettext, \"errors\", \"user error\", \"Error\", \"%{count} errors\", 1)\n      #=> \"Errore\"\n\n  \"\"\"\n  @doc section: :translation\n  @spec dpngettext(module, domain, binary | nil, binary, binary, non_neg_integer, bindings) ::\n          binary\n  def dpngettext(backend, domain, msgctxt, msgid, msgid_plural, n, bindings \\\\ %{})\n\n  def dpngettext(backend, domain, msgctxt, msgid, msgid_plural, n, bindings)\n      when is_list(bindings) do\n    dpngettext(backend, domain, msgctxt, msgid, msgid_plural, n, Map.new(bindings))\n  end\n\n  def dpngettext(backend, domain, msgctxt, msgid, msgid_plural, n, bindings)\n      when is_atom(backend) and is_domain(domain) and is_binary(msgid) and is_binary(msgid_plural) and\n             is_integer(n) and n >= 0 and is_map(bindings) do\n    domain = domain_or_default(backend, domain)\n    locale = get_locale(backend)\n    result = backend.lngettext(locale, domain, msgctxt, msgid, msgid_plural, n, bindings)\n    handle_backend_result(result, backend, locale, domain, msgctxt, msgid)\n  end\n\n  @doc \"\"\"\n  Returns the pluralized message of the given string in the given domain.\n\n  The string is translated and pluralized by the `backend` module.\n\n  The translated string is interpolated based on the `bindings` argument. For\n  more information on how interpolation works, refer to the documentation of the\n  `Gettext` module.\n\n  If the message for the given `msgid` and `msgid_plural` is not found, the\n  `msgid` or `msgid_plural` (based on `n` being singular or plural) is returned\n  (interpolated if necessary).\n\n  ## Examples\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n      Gettext.dngettext(MyApp.Gettext, \"errors\", \"Error\", \"%{count} errors\", 3)\n      #=> \"3 errori\"\n      Gettext.dngettext(MyApp.Gettext, \"errors\", \"Error\", \"%{count} errors\", 1)\n      #=> \"Errore\"\n\n  \"\"\"\n  @doc section: :translation\n  @spec dngettext(module, domain, binary, binary, non_neg_integer, bindings) :: binary\n  def dngettext(backend, domain, msgid, msgid_plural, n, bindings \\\\ %{}),\n    do: dpngettext(backend, domain, nil, msgid, msgid_plural, n, bindings)\n\n  @doc \"\"\"\n  Returns the pluralized message of the given string with a given context\n  in the `\"default\"` domain.\n\n  Works exactly like:\n\n      Gettext.dpngettext(backend, \"default\", context, msgid, msgid_plural, n, bindings)\n\n  \"\"\"\n  @doc section: :translation\n  @spec pngettext(module, binary, binary, binary, non_neg_integer, bindings) :: binary\n  def pngettext(backend, msgctxt, msgid, msgid_plural, n, bindings),\n    do: dpngettext(backend, \"default\", msgctxt, msgid, msgid_plural, n, bindings)\n\n  @doc \"\"\"\n  Returns the pluralized message of the given string in the `\"default\"`\n  domain.\n\n  Works exactly like:\n\n      Gettext.dngettext(backend, \"default\", msgid, msgid_plural, n, bindings)\n\n  \"\"\"\n  @doc section: :translation\n  @spec ngettext(module, binary, binary, non_neg_integer, bindings) :: binary\n  def ngettext(backend, msgid, msgid_plural, n, bindings \\\\ %{}) do\n    dngettext(backend, \"default\", msgid, msgid_plural, n, bindings)\n  end\n\n  @doc \"\"\"\n  Runs `fun` with the global Gettext locale set to `locale`.\n\n  This function just sets the global Gettext locale to `locale` before running\n  `fun` and sets it back to its previous value afterwards. Note that\n  `put_locale/2` is used to set the locale, which is thus set only for the\n  current process (keep this in mind if you plan on spawning processes inside\n  `fun`).\n\n  The value returned by this function is the return value of `fun`.\n\n  ## Examples\n\n      Gettext.put_locale(\"fr\")\n\n      gettext(\"Hello world\")\n      #=> \"Bonjour monde\"\n\n      Gettext.with_locale(\"it\", fn ->\n        gettext(\"Hello world\")\n      end)\n      #=> \"Ciao mondo\"\n\n      gettext(\"Hello world\")\n      #=> \"Bonjour monde\"\n\n  \"\"\"\n  @doc section: :locale\n  @spec with_locale(locale, (-> result)) :: result when result: var\n  def with_locale(locale, fun) when is_binary(locale) and is_function(fun) do\n    previous_locale = Process.get(Gettext)\n    Gettext.put_locale(locale)\n\n    try do\n      fun.()\n    after\n      if previous_locale do\n        Gettext.put_locale(previous_locale)\n      else\n        Process.delete(Gettext)\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Runs `fun` with the Gettext locale set to `locale` for the given `backend`.\n\n  This function just sets the Gettext locale for `backend` to `locale` before\n  running `fun` and sets it back to its previous value afterwards. Note that\n  `put_locale/2` is used to set the locale, which is thus set only for the\n  current process (keep this in mind if you plan on spawning processes inside\n  `fun`).\n\n  The value returned by this function is the return value of `fun`.\n\n  ## Examples\n\n      Gettext.put_locale(MyApp.Gettext, \"fr\")\n\n      gettext(\"Hello world\")\n      #=> \"Bonjour monde\"\n\n      Gettext.with_locale(MyApp.Gettext, \"it\", fn ->\n        gettext(\"Hello world\")\n      end)\n      #=> \"Ciao mondo\"\n\n      gettext(\"Hello world\")\n      #=> \"Bonjour monde\"\n\n  \"\"\"\n  @doc section: :locale\n  @spec with_locale(backend(), locale(), (-> result)) :: result when result: var\n  def with_locale(backend, locale, fun)\n      when is_atom(backend) and is_binary(locale) and is_function(fun) do\n    previous_locale = Process.get(backend)\n    Gettext.put_locale(backend, locale)\n\n    try do\n      fun.()\n    after\n      if previous_locale do\n        Gettext.put_locale(backend, previous_locale)\n      else\n        Process.delete(backend)\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Returns all the locales for which PO files exist for the given `backend`.\n\n  If the messages directory for the given backend doesn't exist, then an\n  empty list is returned.\n\n  ## Examples\n\n  With the following backend:\n\n      defmodule MyApp.Gettext do\n        use Gettext.Backend, otp_app: :my_app\n      end\n\n  and the following messages directory:\n\n      my_app/priv/gettext\n      ├─ en\n      ├─ it\n      └─ pt_BR\n\n  then:\n\n      Gettext.known_locales(MyApp.Gettext)\n      #=> [\"en\", \"it\", \"pt_BR\"]\n\n  \"\"\"\n  @doc section: :locale\n  @spec known_locales(backend()) :: [locale()]\n  def known_locales(backend) when is_atom(backend) do\n    backend.__gettext__(:known_locales)\n  end\n\n  defp handle_backend_result({:ok, string}, _backend, _locale, _domain, _msgctxt, _msgid) do\n    string\n  end\n\n  defp handle_backend_result({:default, string}, _backend, _locale, _domain, _msgctxt, _msgid) do\n    string\n  end\n\n  defp handle_backend_result(\n         {:missing_bindings, incomplete, missing},\n         backend,\n         locale,\n         domain,\n         msgctxt,\n         msgid\n       ) do\n    exception = %MissingBindingsError{\n      backend: backend,\n      locale: locale,\n      domain: domain,\n      msgctxt: msgctxt,\n      msgid: msgid,\n      missing: missing\n    }\n\n    backend.handle_missing_bindings(exception, incomplete)\n  end\n\n  defp domain_or_default(backend, :default), do: backend.__gettext__(:default_domain)\n  defp domain_or_default(_backend, domain) when is_binary(domain), do: domain\nend\n"
  },
  {
    "path": "lib/mix/tasks/compile.gettext.ex",
    "content": "defmodule Mix.Tasks.Compile.Gettext do\n  @moduledoc false\n\n  def run(_args) do\n    IO.warn(\"\"\"\n    the :gettext compiler is no longer required in your mix.exs.\n\n    Please find the following line in your mix.exs and remove the :gettext entry:\n\n        compilers: [..., :gettext, ...] ++ Mix.compilers(),\n    \"\"\")\n\n    {:noop, []}\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/gettext.extract.ex",
    "content": "defmodule Mix.Tasks.Gettext.Extract do\n  use Mix.Task\n  @recursive true\n\n  @shortdoc \"Extracts messages from source code\"\n\n  @moduledoc \"\"\"\n  Extracts messages by recompiling the Elixir source code.\n\n  ```bash\n  mix gettext.extract [OPTIONS]\n  ```\n\n  messages are extracted into POT (Portable Object Template) files with a\n  `.pot` extension. The location of these files is determined by the `:otp_app`\n  and `:priv` options given by Gettext modules when they call `use Gettext`. One\n  POT file is generated for each message domain.\n\n  All automatically-extracted messages are assigned the `elixir-autogen` flag.\n  If a message from the POT is no longer present and has the `elixir-autogen`\n  flag, the message is removed.\n\n  Before `v0.19.0`, the `elixir-format` flag was used to detect automatically\n  extracted messages. This has been deprecated in `v0.19.0`. When extracting\n  with the newest version, the new `elixir-autogen` flag is added to all\n  automatically extracted messages.\n\n  All messages are assigned a format flag. When using the default\n  interpolation module, that flag is `elixir-format`. With other interpolation\n  modules, the flag name is defined by that implementation (see\n  `c:Gettext.Interpolation.message_format/0`).\n\n  If you would like to verify that your POT files are up to date with the\n  current state of the codebase, you can provide the `--check-up-to-date`\n  flag. This is particularly useful for automated checks and in CI systems.\n  This validation will fail even when the same calls to Gettext\n  only change location in the codebase:\n\n  ```bash\n  mix gettext.extract --check-up-to-date\n  ```\n\n  It is possible to pass the `--merge` option to perform merging\n  for every Gettext backend updated during merge:\n\n  ```bash\n  mix gettext.extract --merge\n  ```\n\n  All other options passed to `gettext.extract` are forwarded to the\n  `gettext.merge` task (`Mix.Tasks.Gettext.Merge`), which is called internally\n  by this task. For example:\n\n  ```bash\n  mix gettext.extract --merge --no-fuzzy\n  ```\n\n  \"\"\"\n\n  @switches [merge: :boolean, check_up_to_date: :boolean]\n\n  @impl true\n  def run(args) do\n    Application.ensure_all_started(:gettext)\n    _ = Mix.Project.get!()\n    mix_config = Mix.Project.config()\n    {opts, _} = OptionParser.parse!(args, switches: @switches)\n    pot_files = extract(mix_config[:app], mix_config[:gettext] || [])\n\n    if opts[:check_up_to_date] do\n      run_up_to_date_check(pot_files)\n    else\n      run_message_extraction(pot_files, opts, args)\n    end\n  end\n\n  defp run_message_extraction(pot_files, opts, args) do\n    for {path, {:changed, contents}} <- pot_files do\n      File.mkdir_p!(Path.dirname(path))\n      File.write!(path, contents)\n      Mix.shell().info(\"Extracted #{Path.relative_to_cwd(path)}\")\n    end\n\n    if opts[:merge] do\n      run_merge(pot_files, args)\n    end\n\n    :ok\n  end\n\n  defp run_up_to_date_check(pot_files) do\n    not_extracted_paths = for {path, {:changed, _contents}} <- pot_files, do: path\n\n    if not_extracted_paths == [] do\n      :ok\n    else\n      Mix.raise(\"\"\"\n      mix gettext.extract failed due to --check-up-to-date.\n      The following POT files were not extracted or are out of date:\n\n      #{Enum.map_join(not_extracted_paths, \"\\n\", &\"  * #{&1 |> Path.relative_to_cwd()}\")}\n      \"\"\")\n    end\n  end\n\n  defp extract(app, gettext_config) do\n    Gettext.Extractor.enable()\n    force_compile()\n    Gettext.Extractor.pot_files(app, gettext_config)\n  after\n    Gettext.Extractor.disable()\n  end\n\n  defp force_compile do\n    # For old Elixir versions, we have to clean the manifest,\n    # otherwise we are forced to compile all dependencies.\n    # Elixir v1.19.3 supports the --force-elixir option below.\n    if not Version.match?(System.version(), \">= 1.19.3\") do\n      Mix.Tasks.Compile.Elixir.clean()\n      Enum.each(Mix.Tasks.Compile.Elixir.manifests(), &File.rm/1)\n    end\n\n    # If \"compile\" was never called, the reenabling is a no-op and\n    # \"compile.elixir\" is a no-op as well (because it wasn't reenabled after\n    # running \"compile\"). If \"compile\" was already called, then running\n    # \"compile\" is a no-op and running \"compile.elixir\" will work because we\n    # manually reenabled it.\n    Mix.Task.reenable(\"compile.elixir\")\n    Mix.Task.run(\"compile\", [\"--force-elixir\"])\n    Mix.Task.run(\"compile.elixir\", [\"--force\"])\n  end\n\n  defp run_merge(pot_files, argv) do\n    pot_files\n    |> Enum.map(fn {path, _} -> Path.dirname(path) end)\n    |> Enum.uniq()\n    |> Task.async_stream(&Mix.Tasks.Gettext.Merge.run([&1 | argv]),\n      ordered: false,\n      timeout: 120_000\n    )\n    |> Stream.run()\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/gettext.merge.ex",
    "content": "defmodule Mix.Tasks.Gettext.Merge do\n  use Mix.Task\n  @recursive true\n\n  @shortdoc \"Merge template files into message files\"\n\n  @moduledoc \"\"\"\n  Merges PO/POT files with PO files.\n\n  This task is used when messages in the source code change: when they do,\n  `mix gettext.extract` is usually used to extract the new messages to POT\n  files. At this point, developers or translators can use this task to \"sync\"\n  the newly-updated POT files with the existing locale-specific PO files. All\n  the metadata for each message (like position in the source code, comments,\n  and so on) is taken from the newly-updated POT file; the only things taken\n  from the PO file are the actual translated strings.\n\n  #### Fuzzy Matching\n\n  Messages in the updated PO/POT file that have an exact match (a\n  message with the same `msgid`) in the old PO file are merged as described\n  above. When a message in the updated PO/POT files has no match in the old\n  PO file, Gettext attemps a **fuzzy match** for that message. For example, imagine\n  we have this POT file:\n\n      msgid \"hello, world!\"\n      msgstr \"\"\n\n  and we merge it with this PO file:\n\n      # No exclamation point here in the msgid\n      msgid \"hello, world\"\n      msgstr \"ciao, mondo\"\n\n  Since the two messages are similar, Gettext takes the `msgstr` from the\n  existing message over to the new message, which it however\n  marks as *fuzzy*:\n\n      #, fuzzy\n      msgid \"hello, world!\"\n      msgstr \"ciao, mondo\"\n\n  Generally, a `fuzzy` flag calls for review from a translator.\n\n  Fuzzy matching can be configured (for example, the threshold for message\n  similarity can be tweaked) or disabled entirely. Look at the\n  [\"Options\" section](#module-options).\n\n  ## Usage\n\n  ```bash\n  mix gettext.merge OLD_FILE UPDATED_FILE [OPTIONS]\n  mix gettext.merge DIR [OPTIONS]\n  ```\n\n  If two files are given as arguments, `OLD_FILE` must be a `.po` file and\n  `UPDATE_FILE` must be a `.po`/`.pot` file. The first one is the old PO file,\n  while the second one is the last generated one. They are merged and written\n  over the first file. For example:\n\n  ```bash\n  mix gettext.merge priv/gettext/en/LC_MESSAGES/default.po priv/gettext/default.pot\n  ```\n\n  If only one argument is given, then that argument must be a directory\n  containing Gettext messages (with `.pot` files at the root level alongside\n  locale directories - this is usually a \"backend\" directory used by a Gettext\n  backend, see `Gettext.Backend`). For example:\n\n  ```bash\n  mix gettext.merge priv/gettext\n  ```\n\n  If the `--locale LOCALE` option is given, then only the PO files in\n  `<DIR>/<LOCALE>/LC_MESSAGES` will be merged with the POT files in `DIR`. If no\n  options are given, then all the PO files for all locales under `DIR` are\n  merged with the POT files in `DIR`.\n\n  ## Plural Forms\n\n  By default, Gettext will determine the number of plural forms for newly-generated messages\n  by checking the value of `nplurals` in the `Plural-Forms` header in the existing `.po` file. If\n  a `.po` file doesn't already exist and Gettext is creating a new one or if the `Plural-Forms`\n  header is not in the `.po` file, Gettext will use the number of plural forms that\n  the plural module (see `Gettext.Plural`) returns for the locale of the file being created.\n  The content of the `Plural-Forms` header can be forced through the `--plural-forms-header`\n  option (see below).\n\n  ## Options\n\n    * `--locale` - a string representing a locale. If this is provided, then only the PO\n      files in `<DIR>/<LOCALE>/LC_MESSAGES` will be merged with the POT files in `DIR`. This\n      option can only be given when a single argument is passed to the task\n      (a directory).\n\n    * `--no-fuzzy` - don't perform fuzzy matching when merging files.\n\n    * `--fuzzy-threshold` - a float between `0` and `1` which represents the\n      minimum Jaro distance needed for two messages to be considered a fuzzy\n      match. Overrides the global `:fuzzy_threshold` option (see the docs for\n      `Gettext` for more information on this option).\n\n    * `--plural-forms` - (**deprecated in v0.22.0**) an integer strictly greater than `0`.\n      If this is passed, new messages in the target PO files will have this number of empty\n      plural forms. This is deprecated in favor of passing the `--plural-forms-header`,\n      which contains the whole plural-forms specification. See the \"Plural forms\" section above.\n\n    * `--plural-forms-header` - the content of the `Plural-Forms` header as a string.\n      If this is passed, new messages in the target PO files will use this content\n      to determine the number of plurals. See the [\"Plural Forms\" section](#module-plural-forms).\n\n    * `--on-obsolete` - controls what happens when **obsolete** messages are found.\n      If `mark_as_obsolete`, messages are kept and marked as obsolete.\n      If `delete`, obsolete messages are deleted. Defaults to `delete`.\n\n    * `--store-previous-message-on-fuzzy-match` - controls if the previous\n      messages are recorded on fuzzy matches. Is off by default.\n\n  \"\"\"\n\n  alias Expo.PO\n  alias Gettext.Merger\n\n  @default_fuzzy_threshold 0.8\n\n  @switches [\n    locale: :string,\n    fuzzy: :boolean,\n    fuzzy_threshold: :float,\n    plural_forms_header: :string,\n    on_obsolete: :string,\n    store_previous_message_on_fuzzy_match: :boolean\n  ]\n\n  @impl true\n  def run(args) do\n    Mix.Task.run(\"loadpaths\")\n\n    _ = Mix.Project.get!()\n    gettext_config = Mix.Project.config()[:gettext] || []\n\n    case OptionParser.parse!(args, switches: @switches) do\n      {opts, [po_file, reference_file]} ->\n        merge_two_files(po_file, reference_file, opts, gettext_config)\n\n      {opts, [messages_dir]} ->\n        merge_messages_dir(messages_dir, opts, gettext_config)\n\n      {_opts, _args} ->\n        Mix.raise(\n          \"You can only pass one or two arguments to the \\\"gettext.merge\\\" task. \" <>\n            \"Use `mix help gettext.merge` to see the usage of this task\"\n        )\n    end\n\n    Mix.Task.reenable(\"gettext.merge\")\n  end\n\n  defp merge_two_files(po_file, reference_file, opts, gettext_config) do\n    merging_opts = validate_merging_opts!(opts, gettext_config)\n\n    if Path.extname(po_file) == \".po\" and Path.extname(reference_file) in [\".po\", \".pot\"] do\n      ensure_file_exists!(po_file)\n      ensure_file_exists!(reference_file)\n      locale = locale_from_path(po_file)\n\n      {contents, stats} =\n        merge_files(po_file, reference_file, locale, merging_opts, gettext_config)\n\n      write_file(po_file, contents, stats)\n    else\n      Mix.raise(\"Arguments must be a PO file and a PO/POT file\")\n    end\n  end\n\n  defp merge_messages_dir(dir, opts, gettext_config) do\n    ensure_dir_exists!(dir)\n    merging_opts = validate_merging_opts!(opts, gettext_config)\n\n    if locale = opts[:locale] do\n      merge_locale_dir(dir, locale, merging_opts, gettext_config)\n    else\n      merge_all_locale_dirs(dir, merging_opts, gettext_config)\n    end\n  end\n\n  defp merge_locale_dir(pot_dir, locale, opts, gettext_config) do\n    locale_dir = locale_dir(pot_dir, locale)\n    create_missing_locale_dir(locale_dir)\n    merge_dirs(locale_dir, pot_dir, locale, opts, gettext_config)\n  end\n\n  defp merge_all_locale_dirs(pot_dir, opts, gettext_config) do\n    for locale <- File.ls!(pot_dir), File.dir?(Path.join(pot_dir, locale)) do\n      merge_dirs(locale_dir(pot_dir, locale), pot_dir, locale, opts, gettext_config)\n    end\n  end\n\n  def locale_dir(pot_dir, locale) do\n    Path.join([pot_dir, locale, \"LC_MESSAGES\"])\n  end\n\n  defp merge_dirs(po_dir, pot_dir, locale, opts, gettext_config) do\n    merger = fn pot_file ->\n      po_file = find_matching_po(pot_file, po_dir)\n      {contents, stats} = merge_or_create(pot_file, po_file, locale, opts, gettext_config)\n      write_file(po_file, contents, stats)\n    end\n\n    pot_dir\n    |> Path.join(\"*.pot\")\n    |> Path.wildcard()\n    |> Task.async_stream(merger, ordered: false, timeout: :infinity)\n    |> Stream.run()\n\n    warn_for_po_without_pot(po_dir, pot_dir)\n  end\n\n  defp find_matching_po(pot_file, po_dir) do\n    domain = Path.basename(pot_file, \".pot\")\n    Path.join(po_dir, \"#{domain}.po\")\n  end\n\n  defp merge_or_create(pot_file, po_file, locale, opts, gettext_config) do\n    if File.regular?(po_file) do\n      merge_files(po_file, pot_file, locale, opts, gettext_config)\n    else\n      {new_po, stats} = Merger.new_po_file(po_file, pot_file, locale, opts)\n\n      {new_po\n       |> Merger.prune_references(gettext_config)\n       |> PO.compose(), stats}\n    end\n  end\n\n  defp merge_files(po_file, pot_file, locale, opts, gettext_config) do\n    {merged, stats} =\n      Merger.merge(\n        PO.parse_file!(po_file),\n        PO.parse_file!(pot_file),\n        locale,\n        opts,\n        gettext_config\n      )\n\n    {merged\n     |> Merger.prune_references(gettext_config)\n     |> PO.compose(), stats}\n  end\n\n  defp write_file(path, contents, stats) do\n    File.mkdir_p!(Path.dirname(path))\n    File.write!(path, contents)\n    Mix.shell().info(\"Wrote #{path} (#{format_stats(stats)})\")\n  end\n\n  # Warns for every PO file that has no matching POT file.\n  defp warn_for_po_without_pot(po_dir, pot_dir) do\n    po_dir\n    |> Path.join(\"*.po\")\n    |> Path.wildcard()\n    |> Enum.reject(&po_has_matching_pot?(&1, pot_dir))\n    |> Enum.each(fn po_file ->\n      Mix.shell().info(\"Warning: PO file #{po_file} has no matching POT file in #{pot_dir}\")\n    end)\n  end\n\n  defp po_has_matching_pot?(po_file, pot_dir) do\n    domain = Path.basename(po_file, \".po\")\n    pot_path = Path.join(pot_dir, \"#{domain}.pot\")\n    File.exists?(pot_path)\n  end\n\n  defp ensure_file_exists!(path) do\n    unless File.regular?(path), do: Mix.raise(\"No such file: #{path}\")\n  end\n\n  defp ensure_dir_exists!(path) do\n    unless File.dir?(path), do: Mix.raise(\"No such directory: #{path}\")\n  end\n\n  defp create_missing_locale_dir(dir) do\n    unless File.dir?(dir) do\n      File.mkdir_p!(dir)\n      Mix.shell().info(\"Created directory #{dir}\")\n    end\n  end\n\n  defp validate_merging_opts!(opts, gettext_config) do\n    opts =\n      opts\n      |> Keyword.take([\n        :fuzzy,\n        :fuzzy_threshold,\n        :plural_forms_header,\n        :on_obsolete,\n        :store_previous_message_on_fuzzy_match\n      ])\n      |> Keyword.put_new(:store_previous_message_on_fuzzy_match, false)\n      |> Keyword.put_new(:fuzzy, true)\n      |> Keyword.put_new_lazy(:fuzzy_threshold, fn ->\n        gettext_config[:fuzzy_threshold] || @default_fuzzy_threshold\n      end)\n      |> Keyword.update(:on_obsolete, :delete, &cast_on_obsolete/1)\n\n    threshold = opts[:fuzzy_threshold]\n\n    unless threshold >= 0.0 and threshold <= 1.0 do\n      Mix.raise(\"The :fuzzy_threshold option must be a float >= 0.0 and <= 1.0\")\n    end\n\n    opts\n  end\n\n  defp locale_from_path(path) do\n    parts = Path.split(path)\n    index = Enum.find_index(parts, &(&1 == \"LC_MESSAGES\"))\n    Enum.at(parts, index - 1)\n  end\n\n  defp format_stats(stats) do\n    pluralized = if stats.new == 1, do: \"message\", else: \"messages\"\n\n    \"#{stats.new} new #{pluralized}, #{stats.removed} removed, \" <>\n      \"#{stats.exact_matches} unchanged, #{stats.fuzzy_matches} reworded (fuzzy), \" <>\n      \"#{stats.marked_as_obsolete} marked as obsolete\"\n  end\n\n  defp cast_on_obsolete(\"delete\" = _on_obsolete), do: :delete\n  defp cast_on_obsolete(\"mark_as_obsolete\" = _on_obsolete), do: :mark_as_obsolete\n\n  defp cast_on_obsolete(on_obsolete) do\n    Mix.raise(\"\"\"\n    An invalid value was provided for the option `on_obsolete`.\n    Value: #{inspect(on_obsolete)}\n    Valid Choices: \"delete\" / \"mark_as_obsolete\"\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule Gettext.Mixfile do\n  use Mix.Project\n\n  @version \"1.0.2\"\n\n  @description \"Internationalization and localization through gettext\"\n  @repo_url \"https://github.com/elixir-gettext/gettext\"\n\n  def project do\n    [\n      app: :gettext,\n      version: @version,\n      elixir: \"~> 1.14\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      build_embedded: Mix.env() == :prod,\n      deps: deps(),\n      preferred_cli_env: [coveralls: :test, \"coveralls.html\": :test, \"coveralls.github\": :test],\n      test_coverage: [tool: ExCoveralls],\n\n      # Hex\n      package: hex_package(),\n      description: @description,\n\n      # Docs\n      name: \"gettext\",\n      docs: [\n        source_ref: \"v#{@version}\",\n        main: \"Gettext\",\n        source_url: @repo_url,\n        extras: [\"CHANGELOG.md\"],\n        groups_for_docs: [\n          # Gettext\n          \"Translation Functions\": &(&1[:section] == :translation),\n          \"Locale Functions\": &(&1[:section] == :locale),\n\n          # Gettext.Macros\n          \"Macros with Backend\":\n            &(&1[:module] == Gettext.Macros and to_string(&1[:name]) =~ ~r/_with_backend$/),\n          \"Comment Macros\": &(&1[:module] == Gettext.Macros and &1[:name] == :gettext_comment),\n          \"Extraction Macros\":\n            &(&1[:module] == Gettext.Macros and to_string(&1[:name]) =~ ~r/_noop$/),\n          \"Translation Macros\": &(&1[:module] == Gettext.Macros)\n        ]\n      ]\n    ]\n  end\n\n  def application do\n    [\n      extra_applications: [:logger],\n      env: [default_locale: \"en\", plural_forms: Gettext.Plural],\n      mod: {Gettext.Application, []}\n    ]\n  end\n\n  def hex_package do\n    [\n      maintainers: [\"Andrea Leopardi\", \"Jonatan Männchen\", \"José Valim\"],\n      licenses: [\"Apache-2.0\"],\n      links: %{\n        \"GitHub\" => @repo_url,\n        \"Changelog\" => @repo_url <> \"/blob/main/CHANGELOG.md\"\n      },\n      files: ~w(lib mix.exs *.md)\n    ]\n  end\n\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_other), do: [\"lib\"]\n\n  defp deps do\n    [\n      {:expo, \"~> 0.5.1 or ~> 1.0\"},\n\n      # Dev and test dependencies\n      {:castore, \"~> 1.0\", only: :test},\n      {:jason, \"~> 1.0\", only: :test},\n      {:ex_doc, \"~> 0.19\", only: :dev},\n      {:excoveralls, \"~> 0.18.0\", only: :test}\n    ]\n  end\nend\n"
  },
  {
    "path": "test/fixtures/bad_messages/ru/LC_MESSAGES/errors.po",
    "content": "# Russian has 3 plural forms, only 2 are defined here.\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"Требуется минимум один символ\"\nmsgstr[1] \"Требуется минимум %{count} символов\"\n"
  },
  {
    "path": "test/fixtures/bom.po",
    "content": "﻿# NOTE: the BOM sequence isn't visible in most editors. Just don't change this\n# file :)\nmsgid \"foo\"\nmsgstr \"bar\"\n"
  },
  {
    "path": "test/fixtures/empty.po",
    "content": "\n"
  },
  {
    "path": "test/fixtures/invalid_syntax_error.po",
    "content": "msgid \"foo\"\nmsgstr \"bar\"\n\nmsgstr \"bong\"\n"
  },
  {
    "path": "test/fixtures/invalid_token_error.po",
    "content": "\n\nmsg\n"
  },
  {
    "path": "test/fixtures/multi_messages/es/LC_MESSAGES/default.po",
    "content": "msgid \"Hello world\"\nmsgstr \"Hola mundo\"\n"
  },
  {
    "path": "test/fixtures/multi_messages/it/LC_MESSAGES/default.po",
    "content": "msgid \"Hello world\"\nmsgstr \"Ciao mondo\"\n\nmsgctxt \"test\"\nmsgid \"Hello world\"\nmsgstr \"Ciao mondo\"\n"
  },
  {
    "path": "test/fixtures/multi_messages/it/LC_MESSAGES/errors.po",
    "content": "msgid \"Invalid email address\"\nmsgstr \"Indirizzo email non valido\"\n"
  },
  {
    "path": "test/fixtures/po_editors/poedit.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: POEdit test project\\n\"\n\"POT-Creation-Date: \\n\"\n\"PO-Revision-Date: \\n\"\n\"Last-Translator: Meg Buque\\n\"\n\"Language-Team: Elixir <dummy@example.com>\\n\"\n\"Language: it\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: Poedit 1.7.6\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Poedit-SourceCharset: UTF-8\\n\"\n\nmsgid \"Hello\"\nmsgstr \"Ciao\"\n\nmsgid \"Hello, %{name}!\"\nmsgstr \"Ciao, %{name}!\"\n\nmsgid \"One error\"\nmsgid_plural \"%{count} errors\"\nmsgstr[0] \"Un errore\"\nmsgstr[1] \"%{count} errori\"\n\nmsgid \"String with \\\"double quotes\\\" in it\"\nmsgstr \"Stringa che contiene \\\"doppie virgolette\\\"\"\n"
  },
  {
    "path": "test/fixtures/po_editors/poeditor.com.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: POEditor.com\\n\"\n\"Project-Id-Version: POEditor testing\\n\"\n\"Language: it\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n#. A comment\nmsgid \"Hello\"\nmsgstr \"Ciao\"\n\nmsgid \"Hello, %{name}!\"\nmsgstr \"Ciao, %{name}!\"\n\n#. A plural message\nmsgid \"One error\"\nmsgid_plural \"%{count} errors\"\nmsgstr[0] \"Un errore\"\nmsgstr[1] \"%{count} errori\"\n\nmsgid \"String with \\\"double quotes\\\" in it\"\nmsgstr \"Stringa che contiene \\\"doppie virgolette\\\"\"\n"
  },
  {
    "path": "test/fixtures/single_messages/it/LC_MESSAGES/default.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: it\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\nmsgid \"Hello world\"\nmsgstr \"Ciao mondo\"\n\nmsgctxt \"test\"\nmsgid \"Hello world\"\nmsgstr \"Ciao mondo\"\n\nmsgctxt \"test\"\nmsgid \"Hello %{name}\"\nmsgstr \"Ciao %{name}\"\n\nmsgid \"One new email\"\nmsgid_plural \"%{count} new emails\"\nmsgstr[0] \"Una nuova email\"\nmsgstr[1] \"%{count} nuove email\"\n\nmsgctxt \"test\"\nmsgid \"One new email\"\nmsgid_plural \"%{count} new emails\"\nmsgstr[0] \"Una nuova test email\"\nmsgstr[1] \"%{count} nuove test email\"\n\nmsgid \"Concatenated\" \" and long \"\n  \"string\"\nmsgstr \"Stringa\" \" lunga e \"\n  \"concatenata\"\n\nmsgid \"A\" \" friend\"\nmsgid_plural \"%{count}\" \" friends\"\nmsgstr[0] \"Un\" \" amico\"\nmsgstr[1] \"%{count}\" \" amici\"\n\nmsgid \"Empty msgstr!\"\nmsgstr \"\" \"\"\n\nmsgid \"Not even one msgstr\"\nmsgid_plural \"Not even %{count} msgstrs\"\nmsgstr[0] \"\"\nmsgstr[1] \"<not empty>\"\n"
  },
  {
    "path": "test/fixtures/single_messages/it/LC_MESSAGES/errors.po",
    "content": "msgid \"Invalid email address\"\nmsgstr \"Indirizzo email non valido\"\n\nmsgid \"There was an error\"\nmsgid_plural \"There were %{count} errors\"\nmsgstr[0] \"C'è stato un errore\"\nmsgstr[1] \"Ci sono stati %{count} errori\"\n"
  },
  {
    "path": "test/fixtures/single_messages/it/LC_MESSAGES/interpolations.po",
    "content": "msgid \"Hello %{name}\"\nmsgstr \"Ciao %{name}\"\n\nmsgid \"My name is %{name} and I'm %{age}\"\nmsgstr \"Mi chiamo %{name} e ho %{age} anni\"\n\nmsgid \"You have one message, %{name}\"\nmsgid_plural \"You have %{count} messages, %{name}\"\nmsgstr[0] \"Hai un messaggio, %{name}\"\nmsgstr[1] \"Hai %{count} messaggi, %{name}\"\n\nmsgid \"Month\"\nmsgid_plural \"%{count} months\"\nmsgstr[0] \"Mese\"\nmsgstr[1] \"%{count} mesi\"\n\nmsgctxt \"test\"\nmsgid \"You have one message, %{name}\"\nmsgid_plural \"You have %{count} messages, %{name}\"\nmsgstr[0] \"Hai un messaggio, %{name}\"\nmsgstr[1] \"Hai %{count} messaggi, %{name}\"\n"
  },
  {
    "path": "test/fixtures/single_messages/ja/LC_MESSAGES/errors.po",
    "content": "msgid \"Invalid email address\"\nmsgstr \"無効なメールアドレス\"\n\nmsgid \"There was an error\"\nmsgid_plural \"There were %{count} errors\"\nmsgstr[0] \"%{count} エラーがありました\"\n"
  },
  {
    "path": "test/fixtures/valid.po",
    "content": "msgid \"hello\"\nmsgstr \"ciao\"\n\nmsgid \"how are you,\" \" friend?\"\nmsgstr \"come stai,\"\n  \" amico?\"\n"
  },
  {
    "path": "test/gettext/backend_test.exs",
    "content": "defmodule Gettext.BackendTest do\n  # Some things change the :gettext app environment.\n  use ExUnit.Case, async: false\n\n  import ExUnit.CaptureLog\n\n  alias GettextTest.{\n    Backend,\n    BackendWithDefaultDomain\n  }\n\n  defmodule BackendWithCustomPluralForms do\n    use Gettext.Backend,\n      otp_app: :test_application,\n      priv: \"test/fixtures/single_messages\",\n      plural_forms: GettextTest.CustomPlural\n  end\n\n  defmodule BackendWithCustomCompiledPluralForms do\n    use Gettext.Backend,\n      otp_app: :test_application,\n      priv: \"test/fixtures/single_messages\",\n      plural_forms: GettextTest.CustomCompiledPlural\n  end\n\n  defmodule BackendWithOneModulePerLocale do\n    use Gettext.Backend,\n      otp_app: :test_application,\n      split_module_by: [:locale],\n      split_module_compilation: :parallel,\n      priv: \"test/fixtures/single_messages\"\n  end\n\n  defmodule BackendWithOneModulePerLocaleDomain do\n    use Gettext.Backend,\n      otp_app: :test_application,\n      split_module_by: [:locale, :domain],\n      split_module_compilation: :serial,\n      priv: \"test/fixtures/single_messages\"\n  end\n\n  describe \"use Gettext.Backend\" do\n    test \"creates a backend\" do\n      body =\n        quote do\n          use Gettext.Backend,\n            otp_app: :test_application\n        end\n\n      {:module, mod, _bytecode, :ok} = Module.create(TestBackend, body, __ENV__)\n\n      assert mod.__gettext__(:otp_app) == :test_application\n      assert mod.__info__(:attributes)[:behaviour] == [Gettext.Backend]\n    end\n\n    test \"may define one module per locale\" do\n      import BackendWithOneModulePerLocale, only: [lgettext: 5, lngettext: 7]\n      assert Code.ensure_loaded?(BackendWithOneModulePerLocale.T_it)\n\n      # Found on default domain.\n      assert lgettext(\"it\", \"default\", nil, \"Hello world\", %{}) == {:ok, \"Ciao mondo\"}\n\n      # Found on errors domain.\n      assert lgettext(\"it\", \"errors\", nil, \"Invalid email address\", %{}) ==\n               {:ok, \"Indirizzo email non valido\"}\n\n      # Found with plural form.\n      assert lngettext(\n               \"it\",\n               \"errors\",\n               nil,\n               \"There was an error\",\n               \"There were %{count} errors\",\n               1,\n               %{}\n             ) ==\n               {:ok, \"C'è stato un errore\"}\n\n      # Unknown msgid.\n      assert lgettext(\"it\", \"default\", nil, \"nonexistent\", %{}) == {:default, \"nonexistent\"}\n\n      # Unknown domain.\n      assert lgettext(\"it\", \"unknown\", nil, \"Hello world\", %{}) == {:default, \"Hello world\"}\n\n      # Unknown locale.\n      assert lgettext(\"pt_BR\", \"nonexistent\", nil, \"Hello world\", %{}) ==\n               {:default, \"Hello world\"}\n    end\n\n    test \"may define one module per locale and domain\" do\n      import BackendWithOneModulePerLocaleDomain, only: [lgettext: 5, lngettext: 7]\n      assert Code.ensure_loaded?(BackendWithOneModulePerLocaleDomain.T_it_default)\n\n      # Found on default domain.\n      assert lgettext(\"it\", \"default\", nil, \"Hello world\", %{}) == {:ok, \"Ciao mondo\"}\n\n      # Found on errors domain.\n      assert lgettext(\"it\", \"errors\", nil, \"Invalid email address\", %{}) ==\n               {:ok, \"Indirizzo email non valido\"}\n\n      # Found with plural form.\n      assert lngettext(\n               \"it\",\n               \"errors\",\n               nil,\n               \"There was an error\",\n               \"There were %{count} errors\",\n               1,\n               %{}\n             ) ==\n               {:ok, \"C'è stato un errore\"}\n\n      # Unknown msgid.\n      assert lgettext(\"it\", \"default\", nil, \"nonexistent\", %{}) == {:default, \"nonexistent\"}\n\n      # Unknown domain.\n      assert lgettext(\"it\", \"unknown\", nil, \"Hello world\", %{}) == {:default, \"Hello world\"}\n\n      # Unknown locale.\n      assert lgettext(\"pt_BR\", \"nonexistent\", nil, \"Hello world\", %{}) ==\n               {:default, \"Hello world\"}\n    end\n  end\n\n  describe \"__gettext__/1 (generated)\" do\n    test \"with :priv returns the directory where messages are stored\" do\n      assert Backend.__gettext__(:priv) == \"test/fixtures/single_messages\"\n    end\n\n    test \"with :otp_app returns the OTP app for the given backend\" do\n      assert Backend.__gettext__(:otp_app) == :test_application\n    end\n\n    test \"with :default_domain returns the default domain for the given backend\" do\n      assert Backend.__gettext__(:default_domain) == \"default\"\n      assert BackendWithDefaultDomain.__gettext__(:default_domain) == \"errors\"\n    end\n  end\n\n  describe \"c:lgettext/5\" do\n    test \"returns {:ok, translation} for found translations\" do\n      assert Backend.lgettext(\"it\", \"default\", nil, \"Hello world\", %{}) == {:ok, \"Ciao mondo\"}\n\n      assert Backend.lgettext(\"it\", \"errors\", nil, \"Invalid email address\", %{}) ==\n               {:ok, \"Indirizzo email non valido\"}\n    end\n\n    test \"returns {:default, msgid} for missing translations\" do\n      # Unknown msgid.\n      assert Backend.lgettext(\"it\", \"default\", nil, \"nonexistent\", %{}) ==\n               {:default, \"nonexistent\"}\n\n      # Unknown domain.\n      assert Backend.lgettext(\"it\", \"unknown\", nil, \"Hello world\", %{}) ==\n               {:default, \"Hello world\"}\n\n      # Unknown locale.\n      assert Backend.lgettext(\"pt_BR\", \"nonexistent\", nil, \"Hello world\", %{}) ==\n               {:default, \"Hello world\"}\n    end\n\n    test \"returns {:default, msgid} if the msgstr is an empty string\" do\n      assert Backend.lgettext(\"it\", \"default\", nil, \"Empty msgstr!\", %{}) ==\n               {:default, \"Empty msgstr!\"}\n    end\n\n    test \"supports translating the msgid of plural translations\" do\n      assert Backend.lgettext(\"it\", \"errors\", nil, \"There was an error\", %{}) ==\n               {:ok, \"C'è stato un errore\"}\n    end\n\n    test \"supports interpolation with found translations\" do\n      assert Backend.lgettext(\"it\", \"interpolations\", nil, \"Hello %{name}\", %{name: \"Jane\"}) ==\n               {:ok, \"Ciao Jane\"}\n\n      msgid = \"My name is %{name} and I'm %{age}\"\n\n      assert Backend.lgettext(\"it\", \"interpolations\", nil, msgid, %{name: \"Meg\", age: 33}) ==\n               {:ok, \"Mi chiamo Meg e ho 33 anni\"}\n\n      # A map of bindings is supported as well.\n      assert Backend.lgettext(\"it\", \"interpolations\", nil, \"Hello %{name}\", %{name: \"Jane\"}) ==\n               {:ok, \"Ciao Jane\"}\n    end\n\n    test \"supports interpolation with missing translations\" do\n      msgid = \"Hello %{name}, missing message!\"\n\n      assert Backend.lgettext(\"pl\", \"foo\", nil, msgid, %{name: \"Samantha\"}) ==\n               {:default, \"Hello Samantha, missing message!\"}\n\n      msgid = \"Hello world!\"\n      assert Backend.lgettext(\"pl\", \"foo\", nil, msgid, %{}) == {:default, \"Hello world!\"}\n\n      msgid = \"Hello %{name}\"\n\n      assert Backend.lgettext(\"pl\", \"foo\", nil, msgid, %{}) ==\n               {:missing_bindings, \"Hello %{name}\", [:name]}\n    end\n\n    test \"falls back to handle_missing_translation\" do\n      msgctxt = \"some context\"\n      msgid = \"Hello %{name}\"\n      bindings = %{name: \"Jane\"}\n\n      assert Backend.lgettext(\"pl\", \"foo\", msgctxt, msgid, bindings) ==\n               {:default, \"Hello Jane\"}\n\n      assert_receive {\"pl\", \"foo\", ^msgctxt, ^msgid, ^bindings}\n    end\n\n    test \"preserves the key when using the default c:handle_missing_bindings/2\" do\n      msgid = \"My name is %{name} and I'm %{age}\"\n\n      assert Backend.lgettext(\"it\", \"interpolations\", nil, msgid, %{name: \"José\"}) ==\n               {:missing_bindings, \"Mi chiamo José e ho %{age} anni\", [:age]}\n    end\n\n    test \"strings are concatenated before generating function clauses\" do\n      msgid = \"Concatenated and long string\"\n\n      assert Backend.lgettext(\"it\", \"default\", msgid, %{}) ==\n               {:ok, \"Stringa lunga e concatenata\"}\n\n      assert Backend.lgettext(\"it\", \"default\", nil, msgid, %{}) ==\n               {:ok, \"Stringa lunga e concatenata\"}\n\n      msgid = \"A friend\"\n      msgid_plural = \"%{count} friends\"\n\n      assert Backend.lngettext(\"it\", \"default\", nil, msgid, msgid_plural, 1, %{}) ==\n               {:ok, \"Un amico\"}\n    end\n\n    test \"warns if the domain contains slashes\" do\n      log =\n        capture_log(fn ->\n          assert Backend.lgettext(\"it\", \"sub/dir/domain\", nil, \"hello\", %{}) ==\n                   {:default, \"hello\"}\n        end)\n\n      assert log =~ ~s(Slashes in domains are not supported: \"sub/dir/domain\")\n    end\n\n    test \"with :allowed_locales ignores other locales as strings\" do\n      assert GettextTest.BackendWithAllowedLocalesString.lgettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"Hello world\",\n               %{}\n             ) ==\n               {:default, \"Hello world\"}\n\n      assert GettextTest.BackendWithAllowedLocalesString.lgettext(\n               \"es\",\n               \"default\",\n               nil,\n               \"Hello world\",\n               %{}\n             ) ==\n               {:ok, \"Hola mundo\"}\n    end\n\n    test \"with :allowed_locales ignores other locales as atom\" do\n      assert GettextTest.BackendWithAllowedLocalesAtom.lgettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"Hello world\",\n               %{}\n             ) ==\n               {:default, \"Hello world\"}\n\n      assert GettextTest.BackendWithAllowedLocalesAtom.lgettext(\n               \"es\",\n               \"default\",\n               nil,\n               \"Hello world\",\n               %{}\n             ) ==\n               {:ok, \"Hola mundo\"}\n    end\n  end\n\n  describe \"c:lngettext/7\" do\n    test \"returns {:ok, translation} for found translations\" do\n      message =\n        Backend.lngettext(\n          \"it\",\n          \"errors\",\n          nil,\n          \"There was an error\",\n          \"There were %{count} errors\",\n          1,\n          %{}\n        )\n\n      assert message == {:ok, \"C'è stato un errore\"}\n\n      message =\n        Backend.lngettext(\n          \"it\",\n          \"errors\",\n          nil,\n          \"There was an error\",\n          \"There were %{count} errors\",\n          3,\n          %{}\n        )\n\n      assert message == {:ok, \"Ci sono stati 3 errori\"}\n\n      assert {:ok, \"3 エラーがありました\"} =\n               Backend.lngettext(\n                 \"ja\",\n                 \"errors\",\n                 nil,\n                 \"There was an error\",\n                 \"There were %{count} errors\",\n                 3,\n                 %{}\n               )\n    end\n\n    test \"returns {:default, msgid(_plural)} for missing translations\" do\n      assert Backend.lngettext(\"it\", \"not a domain\", nil, \"foo\", \"foos\", 1, %{}) ==\n               {:default, \"foo\"}\n\n      assert Backend.lngettext(\"it\", \"not a domain\", nil, \"foo\", \"foos\", 10, %{}) ==\n               {:default, \"foos\"}\n    end\n\n    test \"returns {:default, msgid(_plural)} for translations with empty msgstr\" do\n      msgid = \"Not even one msgstr\"\n      msgid_plural = \"Not even %{count} msgstrs\"\n\n      assert Backend.lngettext(\"it\", \"default\", nil, msgid, msgid_plural, 1, %{}) ==\n               {:default, \"Not even one msgstr\"}\n\n      assert Backend.lngettext(\"it\", \"default\", nil, msgid, msgid_plural, 2, %{}) ==\n               {:default, \"Not even 2 msgstrs\"}\n    end\n\n    test \"supports interpolation\" do\n      msgid = \"There was an error\"\n      msgid_plural = \"There were %{count} errors\"\n\n      assert Backend.lngettext(\"it\", \"errors\", nil, msgid, msgid_plural, 1, %{}) ==\n               {:ok, \"C'è stato un errore\"}\n\n      assert Backend.lngettext(\"it\", \"errors\", nil, msgid, msgid_plural, 4, %{}) ==\n               {:ok, \"Ci sono stati 4 errori\"}\n\n      msgid = \"You have one message, %{name}\"\n      msgid_plural = \"You have %{count} messages, %{name}\"\n\n      assert Backend.lngettext(\"it\", \"interpolations\", nil, msgid, msgid_plural, 1, %{\n               name: \"Jane\"\n             }) ==\n               {:ok, \"Hai un messaggio, Jane\"}\n\n      assert Backend.lngettext(\"it\", \"interpolations\", nil, msgid, msgid_plural, 0, %{\n               name: \"Jane\"\n             }) ==\n               {:ok, \"Hai 0 messaggi, Jane\"}\n    end\n\n    test \"supports interpolation with missing translations\" do\n      msgid = \"One error\"\n      msgid_plural = \"%{count} errors\"\n\n      assert Backend.lngettext(\"pl\", \"foo\", nil, msgid, msgid_plural, 1, %{}) ==\n               {:default, \"One error\"}\n\n      assert Backend.lngettext(\"pl\", \"foo\", nil, msgid, msgid_plural, 9, %{}) ==\n               {:default, \"9 errors\"}\n    end\n\n    test \"falls back to c:handle_missing_binding/2\" do\n      msgid = \"You have one message, %{name}\"\n      msgid_plural = \"You have %{count} messages, %{name}\"\n\n      assert Backend.lngettext(\"it\", \"interpolations\", nil, msgid, msgid_plural, 1, %{}) ==\n               {:missing_bindings, \"Hai un messaggio, %{name}\", [:name]}\n\n      assert Backend.lngettext(\"it\", \"interpolations\", nil, msgid, msgid_plural, 6, %{}) ==\n               {:missing_bindings, \"Hai 6 messaggi, %{name}\", [:name]}\n    end\n\n    test \"uses the default c:handle_missing_plural_translation/7 implementation\" do\n      msgctxt = \"some context\"\n      msgid = \"Hello %{name}\"\n      msgid_plural = \"Hello %{name}\"\n      bindings = %{name: \"Jane\"}\n\n      assert Backend.lngettext(\n               \"pl\",\n               \"foo\",\n               msgctxt,\n               msgid,\n               msgid_plural,\n               4,\n               bindings\n             ) ==\n               {:default, \"Hello Jane\"}\n\n      assert_receive {\"pl\", \"foo\", ^msgctxt, ^msgid, ^msgid_plural, 4, ^bindings}\n    end\n\n    test \"warns if the domain contains slashes\" do\n      log =\n        capture_log(fn ->\n          assert Backend.lngettext(\"it\", \"sub/dir/domain\", nil, \"hello\", \"hellos\", 2, %{}) ==\n                   {:default, \"hellos\"}\n        end)\n\n      assert log =~ ~s(Slashes in domains are not supported: \"sub/dir/domain\")\n    end\n\n    test \"supports a custom Gettext.Plural module\" do\n      assert BackendWithCustomPluralForms.lngettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"One new email\",\n               \"%{count} new emails\",\n               1,\n               %{}\n             ) ==\n               {:ok, \"1 nuove email\"}\n\n      assert BackendWithCustomPluralForms.lngettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"One new email\",\n               \"%{count} new emails\",\n               2,\n               %{}\n             ) ==\n               {:ok, \"Una nuova email\"}\n    end\n\n    test \"supports a custom Gettext.Plural module with the context parameter\" do\n      alias BackendWithCustomCompiledPluralForms, as: T\n\n      assert T.lngettext(\"it\", \"default\", nil, \"One new email\", \"%{count} new emails\", 1, %{})\n\n      assert_received {:plural_context, %{plural_forms_header: \"nplurals=2; plural=(n != 1);\"}}\n    end\n\n    test \"supports a custom Gettext.Plural module from app environment\" do\n      Application.put_env(:gettext, :plural_forms, GettextTest.CustomPlural)\n\n      defmodule BackendWithAppPluralForms do\n        use Gettext.Backend, otp_app: :test_application, priv: \"test/fixtures/single_messages\"\n      end\n\n      assert BackendWithAppPluralForms.lngettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"One new email\",\n               \"%{count} new emails\",\n               1,\n               %{}\n             ) ==\n               {:ok, \"1 nuove email\"}\n\n      assert BackendWithAppPluralForms.lngettext(\n               \"it\",\n               \"default\",\n               nil,\n               \"One new email\",\n               \"%{count} new emails\",\n               2,\n               %{}\n             ) ==\n               {:ok, \"Una nuova email\"}\n    after\n      Application.put_env(:gettext, :plural_forms, Gettext.Plural)\n    end\n\n    test \"raises an error if a plural message has no plural form for the given locale\" do\n      log =\n        capture_log(fn ->\n          Code.eval_quoted(\n            quote do\n              defmodule BadTranslations do\n                use Gettext.Backend,\n                  otp_app: :test_application,\n                  priv: \"test/fixtures/bad_messages\"\n              end\n            end\n          )\n        end)\n\n      assert log =~ \"message is missing plural form 2 which is required by the locale \\\"ru\\\"\"\n\n      msgid = \"should be at least %{count} character(s)\"\n      msgid_plural = \"should be at least %{count} character(s)\"\n\n      assert_raise Gettext.PluralFormError,\n                   ~r/plural form 2 is required for locale \\\"ru\\\" but is missing/,\n                   fn ->\n                     # Dynamic module to avoid warnings.\n                     apply(BadTranslations, :lngettext, [\n                       \"ru\",\n                       \"errors\",\n                       nil,\n                       msgid,\n                       msgid_plural,\n                       8,\n                       %{}\n                     ])\n                   end\n    end\n  end\nend\n"
  },
  {
    "path": "test/gettext/extractor_test.exs",
    "content": "defmodule Gettext.ExtractorTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Expo.Message\n  alias Expo.Messages\n  alias Gettext.Extractor\n\n  describe \"merge_pot_files/2\" do\n    @tag :tmp_dir\n    test \"merges two POT files\", %{tmp_dir: tmp_dir} do\n      paths = %{\n        tomerge: Path.join(tmp_dir, \"tomerge.pot\"),\n        ignored: Path.join(tmp_dir, \"ignored.pot\"),\n        new: Path.join(tmp_dir, \"new.pot\")\n      }\n\n      extracted_po_structs = [\n        {paths.tomerge, %Messages{messages: [%Message.Singular{msgid: [\"other\"], msgstr: [\"\"]}]}},\n        {paths.new, %Messages{messages: [%Message.Singular{msgid: [\"new\"], msgstr: [\"\"]}]}}\n      ]\n\n      write_file(paths.tomerge, \"\"\"\n      msgid \"foo\"\n      msgstr \"\"\n      \"\"\")\n\n      write_file(paths.ignored, \"\"\"\n      msgid \"ignored\"\n      msgstr \"\"\n      \"\"\")\n\n      structs =\n        Extractor.merge_pot_files(extracted_po_structs, [paths.tomerge, paths.ignored], [])\n\n      # Unchanged files are not returned\n      assert {_path, :unchanged} = List.keyfind(structs, paths.ignored, 0)\n\n      assert {_, {:changed, contents}} = List.keyfind(structs, paths.tomerge, 0)\n\n      assert IO.iodata_to_binary(contents) == \"\"\"\n             msgid \"foo\"\n             msgstr \"\"\n\n             msgid \"other\"\n             msgstr \"\"\n             \"\"\"\n\n      assert {_, {:changed, contents}} = List.keyfind(structs, paths.new, 0)\n      contents = IO.iodata_to_binary(contents)\n\n      assert contents =~ \"\"\"\n             msgid \"new\"\n             msgstr \"\"\n             \"\"\"\n    end\n\n    @tag :tmp_dir\n    test \"reports the filename if syntax error\", %{tmp_dir: tmp_dir} do\n      path = Path.join(tmp_dir, \"syntax_error.pot\")\n\n      write_file(path, \"\"\"\n      msgid \"foo\"\n\n      msgid \"bar\"\n      msgstr \"\"\n      \"\"\")\n\n      message = ~r/syntax_error\\.pot:3: syntax error before: msgid/\n\n      assert_raise Expo.PO.SyntaxError, message, fn ->\n        Extractor.merge_pot_files([{path, %Messages{messages: []}}], [path], [])\n      end\n    end\n  end\n\n  describe \"merge_template/2\" do\n    test \"non-autogenerated messages are kept\" do\n      # No autogenerated messages\n      message_1 = %Message.Singular{msgid: [\"foo\"], msgstr: [\"bar\"]}\n      message_2 = %Message.Singular{msgid: [\"baz\"], msgstr: [\"bong\"]}\n      message_3 = %Message.Singular{msgid: [\"a\", \"b\"], msgstr: [\"c\", \"d\"]}\n      old = %Messages{messages: [message_1]}\n      new = %Messages{messages: [message_2, message_3]}\n\n      assert Extractor.merge_template(old, new, []) == %Messages{\n               messages: [message_1, message_2, message_3]\n             }\n    end\n\n    test \"allowed messages are kept\" do\n      message_1 = %Message.Singular{\n        msgid: [\"foo\"],\n        msgstr: [\"bar\"],\n        references: [[{\"foo.ex\", 1}, {\"bar.ex\", 1}], [{\"baz.ex\", 1}]],\n        flags: [[\"elixir-autogen\", \"elixir-format\"]]\n      }\n\n      message_2 = %Message.Singular{\n        msgid: [\"baz\"],\n        msgstr: [\"bong\"],\n        references: [{\"web/static/js/app.js\", 10}]\n      }\n\n      old = %Messages{messages: [message_1, message_2]}\n      new = %Messages{messages: []}\n\n      assert Extractor.merge_template(old, new, excluded_refs_from_purging: ~r{^web/static/}) ==\n               %Messages{messages: [message_2]}\n    end\n\n    test \"obsolete autogenerated messages are discarded\" do\n      # Autogenerated messages\n      message_1 = %Message.Singular{\n        msgid: [\"foo\"],\n        msgstr: [\"bar\"],\n        flags: [[\"elixir-autogen\", \"elixir-format\"]]\n      }\n\n      message_2 = %Message.Singular{msgid: [\"baz\"], msgstr: [\"bong\"]}\n      old = %Messages{messages: [message_1]}\n      new = %Messages{messages: [message_2]}\n\n      assert Extractor.merge_template(old, new, []) == %Messages{messages: [message_2]}\n    end\n\n    test \"matching messages are merged\" do\n      ts1 = [\n        %Message.Singular{\n          msgid: [\"matching autogenerated\"],\n          references: [{\"foo.ex\", 2}],\n          flags: [[\"elixir-autogen\"]],\n          extracted_comments: [\"#. Foo\"]\n        },\n        %Message.Singular{msgid: [\"non-matching autogenerated\"], flags: [[\"elixir-autogen\"]]},\n        %Message.Singular{msgid: [\"non-autogenerated\"], references: [{\"foo.ex\", 4}]}\n      ]\n\n      ts2 = [\n        %Message.Singular{msgid: [\"non-matching non-autogenerated\"]},\n        %Message.Plural{\n          msgid: [\"matching autogenerated\"],\n          msgid_plural: [\"matching non-autogenerated 2\"],\n          references: [{\"foo.ex\", 3}],\n          extracted_comments: [\"#. Bar\"],\n          flags: [[\"elixir-autogen\"]]\n        }\n      ]\n\n      assert Extractor.merge_template(\n               %Messages{messages: ts1},\n               %Messages{messages: ts2},\n               []\n             ) ==\n               %Messages{\n                 messages: [\n                   %Message.Plural{\n                     msgid: [\"matching autogenerated\"],\n                     msgid_plural: [\"matching non-autogenerated 2\"],\n                     references: [{\"foo.ex\", 3}],\n                     flags: [[\"elixir-autogen\"]],\n                     extracted_comments: [\"#. Bar\"]\n                   },\n                   %Message.Singular{\n                     msgid: [\"non-autogenerated\"],\n                     references: [{\"foo.ex\", 4}]\n                   },\n                   %Message.Singular{msgid: [\"non-matching non-autogenerated\"]}\n                 ]\n               }\n    end\n\n    test \"headers are taken from the oldest PO file\" do\n      po1 = %Messages{\n        headers: [\"Last-Translator: Foo\", \"Content-Type: text/plain\"],\n        messages: []\n      }\n\n      po2 = %Messages{headers: [\"Last-Translator: Bar\"], messages: []}\n\n      assert Extractor.merge_template(po1, po2, []) == %Messages{\n               headers: [\n                 \"Last-Translator: Foo\",\n                 \"Content-Type: text/plain\"\n               ],\n               messages: []\n             }\n    end\n\n    test \"non-empty msgstrs raise an error\" do\n      po1 = %Messages{messages: [%Message.Singular{msgid: [\"foo\"], msgstr: [\"bar\"]}]}\n      po2 = %Messages{messages: [%Message.Singular{msgid: [\"foo\"], msgstr: [\"bar\"]}]}\n\n      msg = \"message with msgid 'foo' has a non-empty msgstr\"\n\n      assert_raise Gettext.Error, msg, fn ->\n        Extractor.merge_template(po1, po2, [])\n      end\n    end\n\n    test \"order is kept as much as possible\" do\n      # Old messages are kept in the order we find them (except the ones we\n      # remove), and all the new ones are appended after them.\n      foo_message = %Message.Singular{msgid: [\"foo\"], references: [{\"foo.ex\", 1}]}\n\n      msgid = \"Live stream available from %{provider}\"\n\n      po1 = %Messages{\n        messages: [\n          %Message.Singular{msgid: [msgid], references: [{\"reminder.ex\", 160}]},\n          foo_message\n        ]\n      }\n\n      po2 = %Messages{\n        messages: [\n          %Message.Singular{msgid: [\"new message\"]},\n          foo_message,\n          %Message.Singular{msgid: [msgid], references: [{\"live_streaming.ex\", 40}]}\n        ]\n      }\n\n      %Messages{messages: [message_1, ^foo_message, message_2]} =\n        Extractor.merge_template(po1, po2, [])\n\n      assert message_1.msgid == [msgid]\n      assert message_1.references == [{\"live_streaming.ex\", 40}]\n      assert message_2.msgid == [\"new message\"]\n    end\n\n    test \"messages can be ordered alphabetically through the :sort_by_msgid option\" do\n      # Old and new messages are mixed together and ordered alphabetically.\n      foo_message_uppercase = %Message.Singular{msgid: [\"FOO\"], references: [{\"FOO.ex\", 1}]}\n      foo_message = %Message.Singular{msgid: [\"\", \"foo\"], references: [{\"foo.ex\", 1}]}\n\n      bar_message = %Message.Singular{msgid: [\"ba\", \"r\"], references: [{\"bar.ex\", 1}]}\n\n      baz_message = %Message.Plural{\n        msgid: [\"b\", \"az\"],\n        msgid_plural: [\"bazs\"],\n        references: [{\"baz.ex\", 1}]\n      }\n\n      qux_message = %Message.Singular{msgid: [\"qux\", \"\"], references: [{\"bar.ex\", 1}]}\n\n      po1 = %Messages{\n        messages: [\n          foo_message_uppercase,\n          foo_message,\n          qux_message,\n          bar_message\n        ]\n      }\n\n      po2 = %Messages{\n        messages: [\n          baz_message,\n          foo_message,\n          bar_message,\n          foo_message_uppercase\n        ]\n      }\n\n      %Messages{messages: messages} =\n        Extractor.merge_template(po1, po2, sort_by_msgid: :case_sensitive)\n\n      assert Enum.map(messages, &IO.chardata_to_string(&1.msgid)) == ~w(FOO bar baz foo qux)\n    end\n\n    test \"messages can be ordered alphabetically through the :sort_by_msgid_case_insensitive option\" do\n      # Old and new messages are mixed together and ordered alphabetically in a case insensitive fashion.\n      foo_1_message = %Message.Singular{msgid: [\"foo\"], references: [{\"foo.ex\", 1}]}\n      foo_2_message = %Message.Singular{msgid: [\"Foo\"], references: [{\"Foo.ex\", 1}]}\n      foo_3_message = %Message.Singular{msgid: [\"FOO\"], references: [{\"FOO.ex\", 1}]}\n      bar_message = %Message.Singular{msgid: [\"bar\"], references: [{\"bar.ex\", 1}]}\n      qux_message = %Message.Singular{msgid: [\"qux\"], references: [{\"qux.ex\", 1}]}\n\n      po1 = %Messages{\n        messages: [\n          foo_1_message,\n          qux_message,\n          foo_2_message,\n          bar_message,\n          foo_3_message\n        ]\n      }\n\n      po2 = %Messages{\n        messages: [\n          bar_message,\n          foo_1_message,\n          bar_message\n        ]\n      }\n\n      %Messages{messages: messages} =\n        Extractor.merge_template(po1, po2, sort_by_msgid: :case_insensitive)\n\n      assert Enum.map(messages, &IO.chardata_to_string(&1.msgid)) == ~w(bar foo Foo FOO qux)\n    end\n  end\n\n  test \"extraction process\" do\n    refute Extractor.extracting?()\n    Extractor.enable()\n    assert Extractor.extracting?()\n\n    code = \"\"\"\n    defmodule Gettext.ExtractorTest.MyGettext do\n      use Gettext.Backend, otp_app: :test_application\n    end\n\n    defmodule Gettext.ExtractorTest.MyOtherGettext do\n      use Gettext.Backend, otp_app: :test_application, priv: \"messages\"\n    end\n\n    defmodule Foo do\n      require Gettext.Macros\n\n      def bar do\n        Gettext.Macros.gettext_comment(\"some comment\")\n        Gettext.Macros.gettext_comment(\"some other comment\")\n        Gettext.Macros.gettext_comment(\"repeated comment\")\n        Gettext.Macros.gettext_with_backend(Gettext.ExtractorTest.MyGettext, \"foo\")\n        Gettext.Macros.dngettext_with_backend(Gettext.ExtractorTest.MyGettext, \"errors\", \"one error\", \"%{count} errors\", 2)\n        Gettext.Macros.dngettext_with_backend(Gettext.ExtractorTest.MyGettext, \"errors\", \"one error\", \"%{count} errors\", 2)\n        Gettext.Macros.dgettext_with_backend(Gettext.ExtractorTest.MyGettext, \"errors\", \"one error\")\n        Gettext.Macros.gettext_comment(\"one more comment\")\n        Gettext.Macros.gettext_comment(\"repeated comment\")\n        Gettext.Macros.gettext_comment(\"repeated comment\")\n        Gettext.Macros.gettext_with_backend(Gettext.ExtractorTest.MyGettext, \"foo\")\n        Gettext.Macros.dgettext_with_backend(Gettext.ExtractorTest.MyOtherGettext, \"greetings\", \"hi\")\n        Gettext.Macros.pgettext_with_backend(Gettext.ExtractorTest.MyGettext, \"test\", \"context based message\")\n      end\n    end\n    \"\"\"\n\n    Code.compile_string(code, Path.join(File.cwd!(), \"foo.ex\"))\n\n    expected = [\n      {\"priv/gettext/default.pot\",\n       ~S\"\"\"\n       msgid \"\"\n       msgstr \"\"\n\n       #. some comment\n       #. some other comment\n       #. repeated comment\n       #. one more comment\n       #: foo.ex:16\n       #: foo.ex:23\n       #, elixir-autogen, elixir-format\n       msgid \"foo\"\n       msgstr \"\"\n\n       #: foo.ex:25\n       #, elixir-autogen, elixir-format\n       msgctxt \"test\"\n       msgid \"context based message\"\n       msgstr \"\"\n       \"\"\"},\n      {\"priv/gettext/errors.pot\",\n       ~S\"\"\"\n       msgid \"\"\n       msgstr \"\"\n\n       #: foo.ex:17\n       #: foo.ex:18\n       #: foo.ex:19\n       #, elixir-autogen, elixir-format\n       msgid \"one error\"\n       msgid_plural \"%{count} errors\"\n       msgstr[0] \"\"\n       msgstr[1] \"\"\n       \"\"\"},\n      {\"messages/greetings.pot\",\n       ~S\"\"\"\n       msgid \"\"\n       msgstr \"\"\n\n       #: foo.ex:24\n       #, elixir-autogen, elixir-format\n       msgid \"hi\"\n       msgstr \"\"\n       \"\"\"}\n    ]\n\n    # No backends for the unknown app\n    assert [] = Extractor.pot_files(:unknown, [])\n\n    pot_files = Extractor.pot_files(:test_application, [])\n\n    dumped =\n      pot_files\n      |> Enum.reject(&match?({_path, :unchanged}, &1))\n      |> Enum.map(fn {k, {:changed, v}} -> {k, IO.iodata_to_binary(v)} end)\n\n    # We check that dumped strings end with the `expected` string because\n    # there's the informative comment at the start of each dumped string.\n    Enum.each(dumped, fn {path, contents} ->\n      {^path, expected_contents} = List.keyfind(expected, path, 0)\n      assert String.starts_with?(contents, \"## This file is a PO Template file.\")\n      assert contents =~ expected_contents\n    end)\n  after\n    Extractor.disable()\n    refute Extractor.extracting?()\n  end\n\n  test \"warns on conflicting backends\" do\n    refute Extractor.extracting?()\n    Extractor.enable()\n    assert Extractor.extracting?()\n\n    code = \"\"\"\n    defmodule Gettext.ExtractorConflictTest.MyGettext do\n      use Gettext.Backend, otp_app: :test_application\n    end\n\n    defmodule Gettext.ExtractorConflictTest.MyOtherGettext do\n      use Gettext.Backend, otp_app: :test_application\n    end\n\n    defmodule FooConflict do\n      require Gettext.Macros\n\n      def bar do\n        Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyGettext, \"foo\")\n        Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyOtherGettext, \"foo\")\n      end\n    end\n    \"\"\"\n\n    assert ExUnit.CaptureIO.capture_io(:stderr, fn ->\n             Code.compile_string(code, Path.join(File.cwd!(), \"foo_conflict.ex\"))\n             Extractor.pot_files(:test_application, [])\n           end) =~\n             \"the Gettext backend Gettext.ExtractorConflictTest.MyGettext has the same :priv directory as Gettext.ExtractorConflictTest.MyOtherGettext\"\n  after\n    Extractor.disable()\n  end\n\n  test \"warns on conflicting plural messages\" do\n    refute Extractor.extracting?()\n    Extractor.enable()\n    assert Extractor.extracting?()\n\n    code = \"\"\"\n    defmodule Gettext.ExtractorTest.ConflictingPlural.Gettext do\n      use Gettext.Backend, otp_app: :test_conflicting_plural\n    end\n\n    defmodule Gettext.ExtractorTest.ConflictingPlural.Foo do\n      require Gettext.Macros\n\n      def bar do\n        Gettext.Macros.dngettext_with_backend(Gettext.ExtractorTest.ConflictingPlural.Gettext, \"errors\", \"one error\", \"%{count} errors\", 2)\n        Gettext.Macros.dngettext_with_backend(Gettext.ExtractorTest.ConflictingPlural.Gettext, \"errors\", \"one error\", \"multiple errors\", 2)\n      end\n    end\n    \"\"\"\n\n    assert capture_log(fn ->\n             Code.compile_string(code, Path.join(File.cwd!(), \"foo.ex\"))\n           end) =~\n             \"\"\"\n             Plural message for 'one error' is not matching:\n             Using 'multiple errors' instead of '%{count} errors'.\n             References: foo.ex:9, foo.ex:10\n             \"\"\"\n  after\n    Extractor.disable()\n  end\n\n  defp write_file(path, contents) do\n    path |> Path.dirname() |> File.mkdir_p!()\n    File.write!(path, contents)\n  end\nend\n"
  },
  {
    "path": "test/gettext/fuzzy_test.exs",
    "content": "defmodule Gettext.FuzzyTest do\n  use ExUnit.Case, async: true\n\n  alias Gettext.Fuzzy\n  alias Expo.Message\n\n  test \"matcher/1\" do\n    assert Fuzzy.matcher(0.5).({nil, \"foo\"}, {nil, \"foo\"}) == {:match, 1.0}\n    assert Fuzzy.matcher(0.5).({nil, \"foo\"}, {nil, \"bar\"}) == :nomatch\n    assert Fuzzy.matcher(0.0).({nil, \"foo\"}, {nil, \"bar\"}) == {:match, 0.0}\n  end\n\n  describe \"jaro_distance/2\" do\n    test \"compares the distance of the msgid\" do\n      assert Fuzzy.jaro_distance({nil, \"foo\"}, {nil, \"foo\"}) == 1.0\n      assert Fuzzy.jaro_distance({nil, \"foo\"}, {nil, \"foos\"}) > 0.0\n      assert Fuzzy.jaro_distance({nil, \"foo\"}, {nil, \"bar\"}) == 0.0\n    end\n\n    test \"with one message and one plural message, only the msgids are compared\" do\n      assert Fuzzy.jaro_distance({nil, \"foo\"}, {nil, {\"foo\", \"bar\"}}) == 1.0\n      assert Fuzzy.jaro_distance({nil, {\"foo\", \"bar\"}}, {nil, \"foo\"}) == 1.0\n    end\n\n    test \"completely ignores the msgctxt in the key when calculating the distance\" do\n      assert Fuzzy.jaro_distance({\"a\", \"foo\"}, {\"b\", \"foo\"}) == 1.0\n      assert Fuzzy.jaro_distance({\"same\", \"foo\"}, {\"same\", \"bar\"}) == 0.0\n    end\n  end\n\n  describe \"merge/2\" do\n    test \"two messages\" do\n      message_1 = %Message.Singular{msgid: [\"foo\"]}\n      message_2 = %Message.Singular{msgid: [\"foos\"], msgstr: [\"bar\"]}\n\n      assert %Message.Singular{} = message = Fuzzy.merge(message_1, message_2)\n\n      assert message.msgid == [\"foo\"]\n      assert message.msgstr == [\"bar\"]\n      assert Message.has_flag?(message, \"fuzzy\")\n    end\n\n    test \"a message and a plural message\" do\n      message_1 = %Message.Singular{msgid: [\"foo\"]}\n\n      message_2 = %Message.Plural{\n        msgid: [\"foos\"],\n        msgid_plural: [\"bar\"],\n        msgstr: %{0 => [\"a\"], 1 => [\"b\"]}\n      }\n\n      assert %Message.Singular{} = message = Fuzzy.merge(message_1, message_2)\n\n      assert message.msgid == [\"foo\"]\n      assert message.msgstr == [\"a\"]\n      assert Message.has_flag?(message, \"fuzzy\")\n    end\n\n    test \"a plural message and a message\" do\n      message_1 = %Message.Plural{\n        msgid: [\"foos\"],\n        msgid_plural: [\"bar\"],\n        msgstr: %{0 => [], 1 => []}\n      }\n\n      message_2 = %Message.Singular{msgid: [\"foo\"], msgstr: [\"bar\"]}\n\n      assert %Message.Plural{} = message = Fuzzy.merge(message_1, message_2)\n\n      assert message.msgid == [\"foos\"]\n      assert message.msgid_plural == [\"bar\"]\n      assert message.msgstr == %{0 => [\"bar\"], 1 => [\"bar\"]}\n      assert Message.has_flag?(message, \"fuzzy\")\n    end\n\n    test \"two plural messages\" do\n      message_1 = %Message.Plural{msgid: [\"foos\"], msgid_plural: [\"bar\"]}\n\n      message_2 = %Message.Plural{\n        msgid: [\"foo\"],\n        msgid_plural: [\"baz\"],\n        msgstr: %{0 => [\"a\"], 1 => [\"b\"]}\n      }\n\n      assert %Message.Plural{} = message = Fuzzy.merge(message_1, message_2)\n\n      assert message.msgid == [\"foos\"]\n      assert message.msgid_plural == [\"bar\"]\n      assert message.msgstr == %{0 => [\"a\"], 1 => [\"b\"]}\n      assert Message.has_flag?(message, \"fuzzy\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/gettext/interpolation/default_test.exs",
    "content": "defmodule Gettext.Interpolation.DefaultTest do\n  use ExUnit.Case, async: true\n\n  doctest Gettext.Interpolation.Default\n\n  alias Gettext.Interpolation.Default, as: Interpolation\n\n  require Interpolation\n\n  test \"runtime_interpolate/2\" do\n    interpolatable = Interpolation.to_interpolatable(\"%{a} %{b} %{c}\")\n\n    assert Interpolation.runtime_interpolate(interpolatable, %{a: 1, b: :two, c: \"thr ee\"}) ==\n             {:ok, \"1 two thr ee\"}\n\n    assert Interpolation.runtime_interpolate(interpolatable, %{a: \"a\"}) ==\n             {:missing_bindings, \"a %{b} %{c}\", [:b, :c]}\n\n    interpolatable = Interpolation.to_interpolatable(\"%{a} %{a} %{a}\")\n\n    assert Interpolation.runtime_interpolate(interpolatable, %{a: \"foo\"}) == {:ok, \"foo foo foo\"}\n\n    assert Interpolation.runtime_interpolate(interpolatable, %{b: \"bar\"}) ==\n             {:missing_bindings, \"%{a} %{a} %{a}\", [:a]}\n\n    assert Interpolation.runtime_interpolate(\"%{a} %{b} %{c}\", %{a: \"a\"}) ==\n             {:missing_bindings, \"a %{b} %{c}\", [:b, :c]}\n  end\n\n  test \"to_interpolatable/1\" do\n    assert Interpolation.to_interpolatable(\"Hello %{name}\") == [\"Hello \", :name]\n    assert Interpolation.to_interpolatable(\"%{solo}\") == [:solo]\n    assert Interpolation.to_interpolatable(\"%{foo}%{bar} %{baz}\") == [:foo, :bar, \" \", :baz]\n    assert Interpolation.to_interpolatable(\"%{Your name} is cool!\") == [:\"Your name\", \" is cool!\"]\n    assert Interpolation.to_interpolatable(\"foo %{} bar\") == [\"foo %{} bar\"]\n    assert Interpolation.to_interpolatable(\"%{\") == [\"%{\"]\n    assert Interpolation.to_interpolatable(\"abrupt ending %{\") == [\"abrupt ending %{\"]\n\n    assert Interpolation.to_interpolatable(\"incomplete %{ and then some\") ==\n             [\"incomplete %{ and then some\"]\n\n    assert Interpolation.to_interpolatable(\"first empty %{} then %{ incomplete\") ==\n             [\"first empty %{} then %{ incomplete\"]\n\n    assert Interpolation.to_interpolatable(\"\") == []\n  end\n\n  if System.otp_release() >= \"20\" do\n    test \"to_interpolatable/1 with Unicode\" do\n      assert Interpolation.to_interpolatable(\"%{Héllø} there\") ==\n               [String.to_atom(\"Héllø\"), \" there\"]\n    end\n  end\n\n  test \"keys/1\" do\n    # With a string as its argument\n    assert Interpolation.keys(\"Hello %{name}\") == [:name]\n    assert Interpolation.keys(\"It's %{time} here in %{state}\") == [:time, :state]\n    assert Interpolation.keys(\"Hi there %{your name}\") == [:\"your name\"]\n    assert Interpolation.keys(\"Hello %{name} in %{state} goodbye %{name}\") == [:name, :state]\n\n    # With a list of segments as its argument\n    assert Interpolation.keys([\"Hello \", :name, \" it's \", :time, \" goodbye \", :name]) ==\n             [:name, :time]\n  end\n\n  describe \"compile_interpolate/3\" do\n    test \"interpolates complete bindings\" do\n      assert {:ok, \"Hello World!\"} ==\n               Interpolation.compile_interpolate(:translation, \"Hello %{name}!\", %{name: \"World\"})\n    end\n\n    test \"interpolates incomplete bindings\" do\n      assert {:missing_bindings, \"Hello %{name}!\", [:name]} ==\n               Interpolation.compile_interpolate(:translation, \"Hello %{name}!\", %{\n                 unused: \"binding\"\n               })\n    end\n\n    test \"interpolates no bindings\" do\n      assert {:missing_bindings, \"Hello %{name}!\", [:name]} ==\n               Interpolation.compile_interpolate(:translation, \"Hello %{name}!\", %{})\n    end\n\n    test \"rejects dynamic message\" do\n      assert_raise RuntimeError, fn ->\n        Code.eval_quoted(\n          quote do\n            require Interpolation\n\n            Interpolation.compile_interpolate(\n              :translation,\n              \"dynamic message \" <> inspect(make_ref()),\n              %{}\n            )\n          end\n        )\n      end\n    end\n\n    test \"optimizes plural message without count\" do\n      translate = fn bindings ->\n        Interpolation.compile_interpolate(\n          :plural_translation,\n          \"%{count} shoes\",\n          bindings\n        )\n      end\n\n      assert_raise MatchError, fn ->\n        translate.(%{})\n      end\n\n      assert {:ok, \"7 shoes\"} = translate.(%{count: 7})\n    end\n  end\nend\n"
  },
  {
    "path": "test/gettext/macros_test.exs",
    "content": "defmodule Gettext.MacrosTest.Translator do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    priv: \"test/fixtures/single_messages\"\nend\n\ndefmodule Gettext.MacrosTest do\n  use ExUnit.Case, async: true\n  use Gettext, backend: Gettext.MacrosTest.Translator\n\n  import ExUnit.CaptureLog\n\n  require Gettext.Macros, as: Macros\n\n  @backend Gettext.MacrosTest.Translator\n  @gettext_msgid \"Hello world\"\n\n  describe \"gettext/1\" do\n    test \"supports binary-ish msgid at compile-time\" do\n      Gettext.put_locale(@backend, \"it\")\n      assert gettext(\"Hello world\") == \"Ciao mondo\"\n      assert gettext(@gettext_msgid) == \"Ciao mondo\"\n      assert gettext(~s(Hello world)) == \"Ciao mondo\"\n      assert gettext(\"Hello \" <> \"world\") == \"Ciao mondo\"\n      assert gettext(\"Hello \" <> \"wor\" <> \"ld\") == \"Ciao mondo\"\n      assert gettext(<<\"Hello world\">>) == \"Ciao mondo\"\n      assert gettext(<<\"Hello \", \"world\">>) == \"Ciao mondo\"\n      assert gettext(\"Hello \" <> <<\"wor\", \"ld\">>) == \"Ciao mondo\"\n      assert gettext(\"Hello \" <> ~s(world)) == \"Ciao mondo\"\n      assert gettext(~S(Hello ) <> ~s(world)) == \"Ciao mondo\"\n    end\n  end\n\n  describe \"dgettext/3\" do\n    test \"supports binary-ish msgid at compile-time\" do\n      Gettext.put_locale(@backend, \"it\")\n\n      assert dgettext(\"errors\", \"Invalid email address\") == \"Indirizzo email non valido\"\n      keys = %{name: \"Jim\"}\n      assert dgettext(\"interpolations\", \"Hello %{name}\", keys) == \"Ciao Jim\"\n\n      log =\n        capture_log(fn ->\n          assert dgettext(\"interpolations\", \"Hello %{name}\") == \"Ciao %{name}\"\n        end)\n\n      assert log =~ ~s/[error] missing Gettext bindings: [:name]/\n    end\n  end\n\n  describe \"pgettext/3\" do\n    test \"supports test with context based messages\" do\n      Gettext.put_locale(@backend, \"it\")\n      assert pgettext(\"test\", @gettext_msgid) == \"Ciao mondo\"\n      assert pgettext(\"test\", ~s(Hello world)) == \"Ciao mondo\"\n      assert pgettext(\"test\", \"Hello world\", %{}) == \"Ciao mondo\"\n      assert pgettext(\"test\", \"Hello %{name}\", %{name: \"Marco\"}) == \"Ciao Marco\"\n\n      # Missing message\n      assert pgettext(\"test\", \"Hello missing\", %{}) == \"Hello missing\"\n    end\n  end\n\n  test \"pgettext/3, pngettext/4: dynamic context raises\" do\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          pgettext(context, \"Hello world\")\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:context\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          pngettext(context, \"Hello world\", \"Hello world\", 5)\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:context\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n  end\n\n  test \"dpgettext/4, dpngettext/5: dynamic context or dynamic domain raises\" do\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          dpgettext(\"default\", context, \"Hello world\")\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:context\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          domain = \"test\"\n          dpgettext(domain, \"test\", \"Hello world\")\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:domain\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          dpngettext(\"default\", context, \"Hello world\", \"Hello world\", n)\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:context\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          dpngettext(domain, \"test\", \"Hello world\", \"Hello World\", n)\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:domain\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n  end\n\n  test \"dpgettext/4: context and domain based messages\" do\n    Gettext.put_locale(@backend, \"it\")\n    assert dpgettext(\"default\", \"test\", \"Hello world\", %{}) == \"Ciao mondo\"\n  end\n\n  test \"dgettext/3 and dngettext/2: non-binary things at compile-time\" do\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          context = \"test\"\n          dgettext(\"errors\", msgid)\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:msgid\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          msgid_plural = ~s(foo #{1 + 1} bar)\n          dngettext(\"default\", \"foo\", msgid_plural, 1)\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:msgid_plural\"\n    assert message =~ \"Gettext.gettext(Gettext.MacrosTest.Translator, string)\"\n\n    error =\n      assert_raise ArgumentError, fn ->\n        defmodule Sample do\n          use Gettext, backend: Gettext.MacrosTest.Translator\n          domain = \"dynamic_domain\"\n          dgettext(domain, \"hello\")\n        end\n      end\n\n    message = ArgumentError.message(error)\n    assert message =~ \"Gettext macros expect message keys\"\n    assert message =~ \"{:domain\"\n  end\n\n  describe \"dngettext/5\" do\n    test \"translates with plural and domain\" do\n      Gettext.put_locale(@backend, \"it\")\n\n      assert dngettext(\n               \"interpolations\",\n               \"You have one message, %{name}\",\n               \"You have %{count} messages, %{name}\",\n               1,\n               %{name: \"James\"}\n             ) == \"Hai un messaggio, James\"\n\n      assert dngettext(\n               \"interpolations\",\n               \"You have one message, %{name}\",\n               \"You have %{count} messages, %{name}\",\n               2,\n               %{name: \"James\"}\n             ) == \"Hai 2 messaggi, James\"\n\n      assert dngettext(\n               \"interpolations\",\n               \"Month\",\n               \"%{count} months\",\n               2\n             ) == \"2 mesi\"\n    end\n  end\n\n  @ngettext_msgid \"One new email\"\n  @ngettext_msgid_plural \"%{count} new emails\"\n\n  describe \"ngettext/4\" do\n    test \"translates with plural\" do\n      Gettext.put_locale(@backend, \"it\")\n      assert ngettext(\"One new email\", \"%{count} new emails\", 1) == \"Una nuova email\"\n      assert ngettext(\"One new email\", \"%{count} new emails\", 2) == \"2 nuove email\"\n\n      assert ngettext(@ngettext_msgid, @ngettext_msgid_plural, 1) == \"Una nuova email\"\n      assert ngettext(@ngettext_msgid, @ngettext_msgid_plural, 2) == \"2 nuove email\"\n    end\n  end\n\n  describe \"pngettext/4\" do\n    test \"translates with plurals and context\" do\n      Gettext.put_locale(@backend, \"it\")\n\n      assert pngettext(\"test\", \"One new email\", \"%{count} new emails\", 1) ==\n               \"Una nuova test email\"\n\n      assert pngettext(\"test\", \"One new email\", \"%{count} new emails\", 2) ==\n               \"2 nuove test email\"\n\n      assert pngettext(\"test\", @ngettext_msgid, @ngettext_msgid_plural, 1) ==\n               \"Una nuova test email\"\n\n      assert pngettext(\"test\", @ngettext_msgid, @ngettext_msgid_plural, 2) ==\n               \"2 nuove test email\"\n    end\n  end\n\n  test \"the d?n?gettext macros support a kw list for interpolation\" do\n    Gettext.put_locale(@backend, \"it\")\n    assert gettext(\"%{msg}\", msg: \"foo\") == \"foo\"\n  end\n\n  test \"(d)(p)gettext_noop\" do\n    assert dpgettext_noop(\"errors\", \"test\", \"Oops\") == \"Oops\"\n    assert dgettext_noop(\"errors\", \"Oops\") == \"Oops\"\n    assert gettext_noop(\"Hello %{name}!\") == \"Hello %{name}!\"\n  end\n\n  test \"(d)(p)ngettext_noop\" do\n    assert dpngettext_noop(\"errors\", \"test\", \"One error\", \"%{count} errors\") ==\n             {\"One error\", \"%{count} errors\"}\n\n    assert dngettext_noop(\"errors\", \"One error\", \"%{count} errors\") ==\n             {\"One error\", \"%{count} errors\"}\n\n    assert ngettext_noop(\"One message\", \"%{count} messages\") ==\n             {\"One message\", \"%{count} messages\"}\n\n    assert pngettext_noop(\"test\", \"One message\", \"%{count} messages\") ==\n             {\"One message\", \"%{count} messages\"}\n  end\n\n  ## _with_backend variants\n\n  describe \"dpgettext_noop_with_backend/4\" do\n    test \"supports test with context based messages\" do\n      assert Macros.dpgettext_noop_with_backend(@backend, \"test\", \"ctx\", \"Hello world\") ==\n               \"Hello world\"\n    end\n  end\n\n  describe \"dgettext_noop_with_backend/4\" do\n    test \"supports test with context based messages\" do\n      assert Macros.dgettext_noop_with_backend(@backend, \"test\", \"Hello world\") == \"Hello world\"\n    end\n  end\n\n  describe \"pgettext_noop_with_backend/4\" do\n    test \"supports test with context based messages\" do\n      assert Macros.pgettext_noop_with_backend(@backend, \"ctx\", \"Hello world\") == \"Hello world\"\n    end\n  end\n\n  describe \"gettext_noop_with_backend/2\" do\n    test \"supports test with context based messages\" do\n      assert Macros.gettext_noop_with_backend(@backend, \"Hello world\") == \"Hello world\"\n    end\n  end\n\n  describe \"dpngettext_noop_with_backend/5\" do\n    test \"supports test with context based messages\" do\n      assert Macros.dpngettext_noop_with_backend(\n               @backend,\n               \"test\",\n               \"ctx\",\n               \"One message\",\n               \"%{count} messages\"\n             ) ==\n               {\"One message\", \"%{count} messages\"}\n    end\n  end\n\n  describe \"dngettext_noop_with_backend/4\" do\n    test \"supports test with context based messages\" do\n      assert Macros.dngettext_noop_with_backend(\n               @backend,\n               \"test\",\n               \"One message\",\n               \"%{count} messages\"\n             ) ==\n               {\"One message\", \"%{count} messages\"}\n    end\n  end\n\n  describe \"pngettext_noop_with_backend/4\" do\n    test \"supports test with context based messages\" do\n      assert Macros.pngettext_noop_with_backend(\n               @backend,\n               \"ctx\",\n               \"One message\",\n               \"%{count} messages\"\n             ) ==\n               {\"One message\", \"%{count} messages\"}\n    end\n  end\n\n  describe \"ngettext_noop_with_backend/3\" do\n    test \"supports test with context based messages\" do\n      assert Macros.ngettext_noop_with_backend(@backend, \"One message\", \"%{count} messages\") ==\n               {\"One message\", \"%{count} messages\"}\n    end\n  end\n\n  describe \"translation macros *_with_backend\" do\n    setup do\n      Gettext.put_locale(@backend, \"it\")\n      :ok\n    end\n\n    test \"dpgettext_with_backend/5\" do\n      assert Macros.dpgettext_with_backend(@backend, \"default\", \"test\", \"Hello world\", %{}) ==\n               \"Ciao mondo\"\n    end\n\n    test \"dgettext_with_backend/4\" do\n      assert Macros.dgettext_with_backend(@backend, \"default\", \"Hello world\") == \"Ciao mondo\"\n    end\n\n    test \"pgettext_with_backend/4\" do\n      assert Macros.pgettext_with_backend(@backend, \"test\", \"Hello world\") == \"Ciao mondo\"\n    end\n\n    test \"gettext_with_backend/2\" do\n      assert Macros.gettext_with_backend(@backend, \"Hello world\") == \"Ciao mondo\"\n    end\n\n    test \"dpngettext_with_backend/6\" do\n      assert Macros.dpngettext_with_backend(\n               @backend,\n               \"default\",\n               \"test\",\n               \"One new email\",\n               \"%{count} new emails\",\n               1\n             ) == \"Una nuova test email\"\n    end\n\n    test \"dngettext_with_backend/5\" do\n      assert Macros.dngettext_with_backend(\n               @backend,\n               \"default\",\n               \"One new email\",\n               \"%{count} new emails\",\n               1\n             ) == \"Una nuova email\"\n    end\n\n    test \"pngettext_with_backend/5\" do\n      assert Macros.pngettext_with_backend(\n               @backend,\n               \"test\",\n               \"One new email\",\n               \"%{count} new emails\",\n               1\n             ) == \"Una nuova test email\"\n    end\n\n    test \"ngettext_with_backend/4\" do\n      assert Macros.ngettext_with_backend(\n               @backend,\n               \"One new email\",\n               \"%{count} new emails\",\n               1\n             ) == \"Una nuova email\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/gettext/merger_test.exs",
    "content": "defmodule Gettext.MergerTest do\n  use ExUnit.Case, async: true\n\n  import ExUnit.CaptureIO\n\n  alias Expo.Message\n  alias Expo.Messages\n  alias Gettext.Merger\n\n  @opts fuzzy: true, fuzzy_threshold: 0.8\n  @gettext_config []\n  @autogenerated_flags [[\"elixir-format\"]]\n\n  describe \"merge/5\" do\n    test \"headers from the old file are kept\" do\n      old_po = %Messages{\n        headers: [~S(Language: it\\n), ~S(My-Header: my-value\\n)],\n        messages: []\n      }\n\n      new_pot = %Messages{headers: [\"foo\"], messages: []}\n\n      assert {new_po, _stats} = Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n      assert new_po.headers == old_po.headers\n    end\n\n    test \"obsolete messages are discarded (even the manually entered ones)\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"obs_auto\", msgstr: \"foo\", flags: @autogenerated_flags},\n          %Message.Singular{msgid: \"obs_manual\", msgstr: \"foo\"},\n          %Message.Singular{msgid: \"tomerge\", msgstr: \"foo\"}\n        ]\n      }\n\n      new_pot = %Messages{messages: [%Message.Singular{msgid: \"tomerge\", msgstr: \"\"}]}\n\n      assert {%Messages{messages: [message]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.msgid == \"tomerge\"\n      assert message.msgstr == \"foo\"\n\n      assert stats == %{\n               exact_matches: 1,\n               fuzzy_matches: 0,\n               new: 0,\n               removed: 2,\n               marked_as_obsolete: 0\n             }\n    end\n\n    test \"obsolete messages are marked as obsolete\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"obs_auto\", msgstr: \"foo\", flags: @autogenerated_flags},\n          %Message.Singular{msgid: \"obs_manual\", msgstr: \"foo\"},\n          %Message.Singular{msgid: \"tomerge\", msgstr: \"foo\", obsolete: true}\n        ]\n      }\n\n      new_pot = %Messages{messages: [%Message.Singular{msgid: \"tomerge\", msgstr: \"\"}]}\n\n      assert {%Messages{\n                messages: [\n                  %Message.Singular{msgid: \"tomerge\", obsolete: false},\n                  %Message.Singular{msgid: \"obs_auto\", obsolete: true},\n                  %Message.Singular{msgid: \"obs_manual\", obsolete: true}\n                ]\n              },\n              stats} =\n               Merger.merge(\n                 old_po,\n                 new_pot,\n                 \"en\",\n                 @opts ++ [on_obsolete: :mark_as_obsolete],\n                 @gettext_config\n               )\n\n      assert stats == %{\n               exact_matches: 1,\n               fuzzy_matches: 0,\n               new: 0,\n               removed: 0,\n               marked_as_obsolete: 2\n             }\n    end\n\n    test \"when messages match, the msgstr of the old one is preserved\" do\n      old_po = %Messages{messages: [%Message.Singular{msgid: \"foo\", msgstr: \"bar\"}]}\n      new_pot = %Messages{messages: [%Message.Singular{msgid: \"foo\", msgstr: \"\"}]}\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.msgid == \"foo\"\n      assert message.msgstr == \"bar\"\n    end\n\n    test \"when messages match, existing translator comments are preserved\" do\n      # Note that the new message *should* not have any translator comments\n      # (comes from a POT file).\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", comments: [\"# existing comment\"]}\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", comments: [\"# new comment\"]}\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.msgid == \"foo\"\n      assert message.comments == [\"# existing comment\"]\n    end\n\n    test \"when messages match, existing translator flags are preserved\" do\n      old_po = %Messages{\n        messages: [%Message.Singular{msgid: \"foo\", flags: [[\"fuzzy\"]]}]\n      }\n\n      new_pot = %Messages{messages: [%Message.Singular{msgid: \"foo\"}]}\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert Message.has_flag?(message, \"fuzzy\")\n    end\n\n    test \"when messages match, existing extracted comments are replaced by new ones\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"foo\",\n            extracted_comments: [\"#. existing comment\", \"#. other existing comment\"]\n          }\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", extracted_comments: [\"#. new comment\"]}\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.extracted_comments == [\"#. new comment\"]\n    end\n\n    test \"when messages match, existing references are replaced by new ones\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", references: [{\"foo.ex\", 1}]}\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", references: [{\"bar.ex\", 1}]}\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.references == [{\"bar.ex\", 1}]\n    end\n\n    test \"when messages match, existing flags are replaced by new ones\" do\n      old_po = %Messages{messages: [%Message.Singular{msgid: \"foo\"}]}\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", flags: @autogenerated_flags}\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.flags == @autogenerated_flags\n    end\n\n    test \"messages with same msgid but different msgctxt are completely different\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", msgstr: \"no context\"},\n          %Message.Singular{\n            msgid: \"foo\",\n            msgctxt: \"context\",\n            msgstr: \"with context\"\n          }\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"foo\", msgctxt: \"context\", msgstr: \"\"},\n          %Message.Singular{msgid: \"foo\", msgctxt: \"other context\", msgstr: \"\"},\n          %Message.Singular{msgid: \"foo\", msgstr: \"\"}\n        ]\n      }\n\n      assert {%Messages{messages: messages}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert [\n               %Message.Singular{msgid: \"foo\", msgctxt: \"context\", msgstr: \"with context\"},\n               %Message.Singular{msgid: \"foo\", msgctxt: \"other context\", msgstr: \"no context\"} =\n                 _fuzzy,\n               %Message.Singular{msgid: \"foo\", msgstr: \"no context\"}\n             ] = messages\n    end\n\n    test \"new messages are fuzzy-matched against obsolete messages\" do\n      old_message = %Message.Singular{\n        msgid: [\"hello world!\"],\n        msgstr: [\"foo\"],\n        comments: [\"# existing comment\"],\n        extracted_comments: [\"#. existing comment\"],\n        references: [{\"foo.ex\", 1}]\n      }\n\n      old_po = %Messages{messages: [old_message]}\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"hello worlds!\",\n            references: [{\"foo.ex\", 2}],\n            extracted_comments: [\"#. new comment\"],\n            flags: [[\"my-flag\"]]\n          }\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert %{exact_matches: 0, fuzzy_matches: 1, new: 0, removed: 0} = stats\n\n      assert message.msgid == \"hello worlds!\"\n      assert message.msgstr == [\"foo\"]\n      assert message.comments == [\"# existing comment\"]\n      assert message.extracted_comments == [\"#. new comment\"]\n      assert message.references == [{\"foo.ex\", 2}]\n      assert message.flags == [[\"my-flag\", \"fuzzy\"]]\n      assert message.previous_messages == []\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(\n                 old_po,\n                 new_pot,\n                 \"en\",\n                 @opts ++ [store_previous_message_on_fuzzy_match: true],\n                 @gettext_config\n               )\n\n      assert message.previous_messages == [old_message]\n    end\n\n    test \"exact matches have precedence over fuzzy matches\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: [\"hello world!\"], msgstr: [\"foo\"]},\n          %Message.Singular{msgid: [\"hello worlds!\"], msgstr: [\"bar\"]}\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [%Message.Singular{msgid: [\"hello world!\"]}]\n      }\n\n      # Let's check that the \"hello worlds!\" message is discarded even if it's\n      # a fuzzy match for \"hello world!\".\n      assert {%Messages{messages: [message]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      refute Message.has_flag?(message, \"fuzzy\")\n      assert message.msgid == [\"hello world!\"]\n      assert message.msgstr == [\"foo\"]\n\n      assert %{exact_matches: 1, fuzzy_matches: 0, new: 0, removed: 1} = stats\n    end\n\n    test \"exact matches do not prevent fuzzy matches for other messages\" do\n      old_po = %Messages{\n        messages: [%Message.Singular{msgid: [\"hello world\"], msgstr: [\"foo\"]}]\n      }\n\n      # \"hello world\" will match exactly.\n      # \"hello world!\" should still get a fuzzy match.\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: [\"hello world\"]},\n          %Message.Singular{msgid: [\"hello world!\"]}\n        ]\n      }\n\n      assert {%Messages{messages: [message_1, message_2]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message_1.msgid == [\"hello world\"]\n      assert message_1.msgstr == [\"foo\"]\n      refute Message.has_flag?(message_1, \"fuzzy\")\n\n      assert message_2.msgid == [\"hello world!\"]\n      assert message_2.msgstr == [\"foo\"]\n      assert Message.has_flag?(message_2, \"fuzzy\")\n\n      assert stats.new == 0\n      assert stats.removed == 0\n      assert stats.fuzzy_matches == 1\n      assert stats.exact_matches == 1\n    end\n\n    test \"multiple messages can fuzzy match against a single message\" do\n      old_po = %Messages{\n        messages: [%Message.Singular{msgid: [\"hello world\"], msgstr: [\"foo\"]}]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: [\"hello world 1\"]},\n          %Message.Singular{msgid: [\"hello world 2\"]}\n        ]\n      }\n\n      assert {%Messages{messages: [message_1, message_2]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message_1.msgid == [\"hello world 1\"]\n      assert message_1.msgstr == [\"foo\"]\n      assert Message.has_flag?(message_1, \"fuzzy\")\n\n      assert message_2.msgid == [\"hello world 2\"]\n      assert message_2.msgstr == [\"foo\"]\n      assert Message.has_flag?(message_2, \"fuzzy\")\n\n      assert %{exact_matches: 0, new: 0, fuzzy_matches: 2, removed: 0} = stats\n    end\n\n    test \"filling in a fuzzy message preserves references\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: [\"hello world!\"],\n            msgstr: [\"foo\"],\n            comments: [\"# old comment\"],\n            references: [{\"old_file.txt\", 1}]\n          }\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: [\"hello worlds!\"],\n            references: [{\"new_file.txt\", 2}]\n          }\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert Message.has_flag?(message, \"fuzzy\")\n      assert message.msgid == [\"hello worlds!\"]\n      assert message.msgstr == [\"foo\"]\n      assert message.comments == [\"# old comment\"]\n      assert message.references == [{\"new_file.txt\", 2}]\n    end\n\n    test \"simple messages can be a fuzzy match for plurals\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: [\"Here are {count} cocoa balls.\"],\n            msgstr: [\"Hier sind {count} Kakaokugeln.\"],\n            comments: [\"# Guyanese Cocoballs\"],\n            references: [{\"old_file.txt\", 1}]\n          }\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Plural{\n            msgid: [\"Here is a cocoa ball.\"],\n            msgid_plural: [\"Here are {count} cocoa balls.\"],\n            references: [{\"new_file.txt\", 2}]\n          }\n        ]\n      }\n\n      assert {%Messages{messages: [message]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert Message.has_flag?(message, \"fuzzy\")\n      assert message.msgid == [\"Here is a cocoa ball.\"]\n      assert message.msgid_plural == [\"Here are {count} cocoa balls.\"]\n      assert message.msgstr[0] == [\"Hier sind {count} Kakaokugeln.\"]\n      assert message.comments == [\"# Guyanese Cocoballs\"]\n      assert message.references == [{\"new_file.txt\", 2}]\n\n      assert %{exact_matches: 0, fuzzy_matches: 1, new: 0, removed: 0} = stats\n    end\n\n    # This has been verified with msgmerge too.\n    test \"messages fuzzy-match regardless of msgctxt\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"hello world!\", msgctxt: \"context\", msgstr: [\"cfoo\"]}\n        ]\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"hello worlds!\",\n            msgctxt: \"completely different\"\n          },\n          %Message.Singular{msgid: \"different\", msgctxt: \"context\"}\n        ]\n      }\n\n      assert {%Messages{messages: [fuzzy_message, new_message]}, stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert %{exact_matches: 0, fuzzy_matches: 1, new: 1, removed: 0} = stats\n\n      assert fuzzy_message.msgid == \"hello worlds!\"\n      assert fuzzy_message.msgstr == [\"cfoo\"]\n      assert fuzzy_message.msgctxt == \"completely different\"\n      assert fuzzy_message.flags == [[\"fuzzy\"]]\n\n      assert new_message.msgid == \"different\"\n      assert new_message.msgctxt == \"context\"\n    end\n\n    test \"if there's a Plural-Forms header, it's used to determine number of plural forms\" do\n      old_po = %Messages{\n        headers: [~s(Plural-Forms: nplurals=3;plural=n>1;)],\n        messages: []\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"a\"},\n          %Message.Plural{msgid: \"b\", msgid_plural: \"bs\"}\n        ]\n      }\n\n      assert {%Messages{messages: [message, plural_message]}, _stats} =\n               Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n      assert message.msgid == \"a\"\n\n      assert plural_message.msgid == \"b\"\n      assert plural_message.msgid_plural == \"bs\"\n      assert plural_message.msgstr == %{0 => [\"\"], 1 => [\"\"], 2 => [\"\"]}\n    end\n\n    test \"if there's a Plural-Forms header with only nplurals=<int>, it's used but deprecated\" do\n      old_po = %Messages{\n        headers: [~s(Plural-Forms: nplurals=3)],\n        messages: []\n      }\n\n      new_pot = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"a\"},\n          %Message.Plural{msgid: \"b\", msgid_plural: \"bs\"}\n        ]\n      }\n\n      stderr =\n        capture_io(:stderr, fn ->\n          assert {%Messages{messages: [message, plural_message]}, _stats} =\n                   Merger.merge(old_po, new_pot, \"en\", @opts, @gettext_config)\n\n          assert message.msgid == \"a\"\n\n          assert plural_message.msgid == \"b\"\n          assert plural_message.msgid_plural == \"bs\"\n          assert plural_message.msgstr == %{0 => [\"\"], 1 => [\"\"], 2 => [\"\"]}\n        end)\n\n      assert stderr =~ ~s(Plural-Forms headers in the form \"nplurals=<int>\")\n    end\n\n    test \"custom flags defined by :custom_flag_to_keep config are kept\" do\n      old_po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"a\",\n            flags: [[\"elixir-format\", \"fuzzy\", \"custom-flag\", \"other-custom-flag\"]]\n          }\n        ]\n      }\n\n      new_po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"a\",\n            flags: [[\"elixir-format\"]]\n          }\n        ]\n      }\n\n      gettext_config = [custom_flags_to_keep: [\"custom-flag\"]]\n\n      {merged_message, _stats} = Merger.merge(old_po, new_po, \"en\", @opts, gettext_config)\n\n      assert %Messages{\n               messages: [\n                 %Message.Singular{flags: [[\"elixir-format\", \"fuzzy\", \"custom-flag\"]]}\n               ]\n             } = merged_message\n    end\n  end\n\n  describe \"prune_references/2\" do\n    test \"prunes all references when `write_reference_comments` is `false`\" do\n      po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"a\", references: [[{\"path/to/file.ex\", 12}]]},\n          %Message.Plural{msgid: \"a\", msgid_plural: \"ab\", references: [[{\"path/to/file.ex\", 12}]]}\n        ]\n      }\n\n      config = [write_reference_comments: false]\n\n      assert %Messages{\n               messages: [\n                 %Message.Singular{references: []},\n                 %Message.Plural{references: []}\n               ]\n             } = Merger.prune_references(po, config)\n    end\n\n    test \"prunes reference line numbers when `write_reference_line_numbers` is `false`\" do\n      po = %Messages{\n        messages: [\n          %Message.Singular{\n            msgid: \"a\",\n            references: [\n              [{\"path/to/file.ex\", 12}, {\"path/to/file.ex\", 24}, {\"a\", 1}],\n              [{\"path/to/file.ex\", 42}, {\"b\", 1}],\n              [{\"path/to/file.ex\", 42}],\n              [{\"path/to/other_file.ex\", 24}]\n            ]\n          },\n          %Message.Plural{msgid: \"a\", msgid_plural: \"ab\", references: [[{\"path/to/file.ex\", 12}]]}\n        ]\n      }\n\n      config = [write_reference_line_numbers: false]\n\n      assert %Messages{\n               messages: [\n                 %Message.Singular{\n                   references: [\n                     [\"path/to/file.ex\", \"a\"],\n                     [\"b\"],\n                     [\"path/to/other_file.ex\"]\n                   ]\n                 },\n                 %Message.Plural{references: [[\"path/to/file.ex\"]]}\n               ]\n             } = Merger.prune_references(po, config)\n    end\n\n    test \"does nothing per default\" do\n      po = %Messages{\n        messages: [\n          %Message.Singular{msgid: \"a\", references: [[{\"path/to/file.ex\", 12}]]},\n          %Message.Plural{msgid: \"a\", msgid_plural: \"ab\", references: [{\"path/to/file.ex\", 12}]}\n        ]\n      }\n\n      config = []\n\n      assert po == Merger.prune_references(po, config)\n    end\n  end\n\n  @tag :tmp_dir\n  test \"new_po_file/2\", %{tmp_dir: tmp_dir} do\n    pot_path = Path.join(tmp_dir, \"new_po_file.pot\")\n    new_po_path = Path.join(tmp_dir, \"it/LC_MESSAGES/new_po_file.po\")\n\n    write_file(pot_path, \"\"\"\n    ## Stripme!\n    # A comment\n    msgid \"foo\"\n    msgstr \"bar\"\n\n    msgid \"plural\"\n    msgid_plural \"plurals\"\n    msgstr[0] \"\"\n    msgstr[1] \"\"\n\n    msgctxt \"my_context\"\n    msgid \"with context\"\n    msgstr \"\"\n    \"\"\")\n\n    {new_po, _stats} = Merger.new_po_file(new_po_path, pot_path, \"it\", [plural_forms: 1] ++ @opts)\n\n    assert new_po.file == new_po_path\n    assert new_po.headers == [\"\", \"Language: it\\n\", \"Plural-Forms: nplurals=1\\n\"]\n\n    assert [\"# \\\"msgid\\\"s in this file come from POT (.pot) files.\", \"##\" | _] =\n             new_po.top_comments\n\n    assert [\n             %Message.Singular{} = message,\n             %Message.Plural{} = plural_message,\n             %Message.Singular{} = context_message\n           ] = new_po.messages\n\n    assert message.comments == [\" A comment\"]\n    assert message.msgid == [\"foo\"]\n    assert message.msgstr == [\"bar\"]\n\n    assert plural_message.msgid == [\"plural\"]\n    assert plural_message.msgid_plural == [\"plurals\"]\n    assert plural_message.msgstr == %{0 => [\"\"]}\n\n    assert context_message.msgctxt == [\"my_context\"]\n    assert context_message.msgid == [\"with context\"]\n  end\n\n  defp write_file(path, contents) do\n    path |> Path.dirname() |> File.mkdir_p!()\n    File.write!(path, contents)\n  end\nend\n"
  },
  {
    "path": "test/gettext/new_backend_setup_test.exs",
    "content": "# https://github.com/elixir-gettext/gettext/issues/330\ndefmodule Gettext.NewBackendSetupTest do\n  # Has to be async: false since it changes Elixir compiler options.\n  use ExUnit.Case, async: false\n\n  @moduletag :tmp_dir\n\n  defmodule Backend do\n    use Gettext.Backend, otp_app: :test_application\n  end\n\n  describe \"use Gettext, backend: ...\" do\n    test \"imports Gettext.Macros macros but doesn't generate functions\" do\n      {{:module, mod, _bytecode, _funs}, _bindings} =\n        Code.eval_quoted(\n          quote do\n            defmodule MyModule do\n              use Gettext,\n                backend: Gettext.NewBackendSetupTest.Backend\n\n              def translate do\n                gettext(\"Hello world\")\n              end\n            end\n          end\n        )\n\n      refute function_exported?(mod, :__gettext__, 1)\n\n      assert mod.translate() == \"Hello world\"\n    end\n  end\n\n  describe \"compile-time dependencies\" do\n    test \"are not created for modules that use the backend\", %{test: test} do\n      top_level_module = :\"Elixir.Gettext_#{test}\"\n      backend_module = Module.concat(top_level_module, Gettext)\n\n      Code.eval_quoted(\n        quote do\n          defmodule unquote(backend_module) do\n            use Gettext.Backend, otp_app: unquote(test)\n          end\n        end\n      )\n\n      old_compiler_opts = Code.compiler_options(tracers: [__MODULE__])\n      on_exit(fn -> Code.compiler_options(old_compiler_opts) end)\n\n      Code.compile_quoted(\n        quote do\n          defmodule unquote(top_level_module) do\n            use Gettext, backend: unquote(backend_module)\n          end\n        end\n      )\n\n      refute_received {:trace, {:require, _meta, ^backend_module, _opts}}\n      refute_received {:trace, {:import, _meta, ^backend_module, _opts}}\n    end\n  end\n\n  def trace(event, _env) do\n    send(self(), {:trace, event})\n    :ok\n  end\nend\n"
  },
  {
    "path": "test/gettext/plural_test.exs",
    "content": "defmodule Gettext.PluralTest do\n  use ExUnit.Case, async: true\n\n  import Gettext.Plural, only: [nplurals: 1, plural: 2]\n\n  alias Gettext.Plural.UnknownLocaleError\n\n  doctest Gettext.Plural\n\n  test \"x_* locales are pluralized like x except for exceptions\" do\n    assert nplurals(\"en\") == nplurals(\"en_GB\")\n\n    assert plural(\"pt\", 0) == 1\n    assert plural(\"pt\", 1) == 0\n    assert plural(\"pt_BR\", 0) == 0\n    assert plural(\"pt_BR\", 1) == 0\n  end\n\n  test \"locale with a territory\" do\n    # The _XX in en_XX gets stripped and en_XX is pluralized as en.\n    assert nplurals(\"en_XX\") == nplurals(\"en\")\n    assert plural(\"en_XX\", 100) == plural(\"en\", 100)\n  end\n\n  test \"unknown locale\" do\n    message = ~r/unknown locale \"wat\"/\n    assert_raise UnknownLocaleError, message, fn -> nplurals(\"wat\") end\n    assert_raise UnknownLocaleError, message, fn -> plural(\"wat\", 1) end\n\n    # This happens with dash as the territory/locale separator\n    # (https://en.wikipedia.org/wiki/IETF_language_tag).\n    message = ~r/unknown locale \"en-us\"/\n    assert_raise UnknownLocaleError, message, fn -> nplurals(\"en-us\") end\n  end\n\n  test \"locales with one form\" do\n    assert nplurals(\"ja\") == 1\n    assert plural(\"ja\", 0) == 0\n    assert plural(\"ja\", 8) == 0\n  end\n\n  test \"locales with two forms where 0 is same as > 1\" do\n    assert nplurals(\"it\") == 2\n    assert plural(\"it\", 1) == 0\n    assert plural(\"it\", 0) == 1\n    assert plural(\"it\", 13) == 1\n  end\n\n  test \"locales with two forms where 0 and 1 are the same\" do\n    assert nplurals(\"fr\") == 2\n    assert plural(\"fr\", 0) == 0\n    assert plural(\"fr\", 1) == 0\n    assert plural(\"fr\", 2) == 1\n  end\n\n  test \"locales that belong to the 3-forms slavic family\" do\n    assert nplurals(\"ru\") == 3\n    assert plural(\"ru\", 21) == 0\n    assert plural(\"ru\", 42) == 1\n    assert plural(\"ru\", 11) == 2\n  end\n\n  test \"locales that belong to the alternative 3-forms slavic family\" do\n    assert nplurals(\"cs\") == 3\n    assert plural(\"cs\", 1) == 0\n    assert plural(\"cs\", 3) == 1\n    assert plural(\"cs\", 12) == 2\n  end\n\n  test \"locales that don't belong to any pluralization family\" do\n    assert plural(\"ar\", 0) == 0\n    assert plural(\"ar\", 1) == 1\n    assert plural(\"ar\", 2) == 2\n    assert plural(\"ar\", 505) == 3\n    assert plural(\"ar\", 733) == 4\n    assert plural(\"ar\", 101) == 5\n\n    assert plural(\"csb\", 1) == 0\n    assert plural(\"csb\", 33) == 1\n    assert plural(\"csb\", 115) == 2\n\n    assert plural(\"cy\", 1) == 0\n    assert plural(\"cy\", 2) == 1\n    assert plural(\"cy\", 23) == 2\n    assert plural(\"cy\", 8) == 3\n\n    assert plural(\"ga\", 1) == 0\n    assert plural(\"ga\", 2) == 1\n    assert plural(\"ga\", 4) == 2\n    assert plural(\"ga\", 10) == 3\n    assert plural(\"ga\", 133) == 4\n\n    assert plural(\"gd\", 1) == 0\n    assert plural(\"gd\", 12) == 1\n    assert plural(\"gd\", 18) == 2\n    assert plural(\"gd\", 20) == 3\n\n    assert plural(\"is\", 71) == 0\n    assert plural(\"is\", 11) == 1\n\n    assert plural(\"jv\", 0) == 0\n    assert plural(\"jv\", 13) == 1\n\n    assert plural(\"kw\", 1) == 0\n    assert plural(\"kw\", 2) == 1\n    assert plural(\"kw\", 3) == 2\n    assert plural(\"kw\", 99) == 3\n\n    assert plural(\"lt\", 81) == 0\n    assert plural(\"lt\", 872) == 1\n    assert plural(\"lt\", 112) == 2\n\n    assert plural(\"lv\", 31) == 0\n    assert plural(\"lv\", 9) == 1\n    assert plural(\"lv\", 0) == 2\n\n    assert plural(\"mk\", 131) == 0\n    assert plural(\"mk\", 132) == 1\n    assert plural(\"mk\", 9) == 2\n\n    assert plural(\"mnk\", 0) == 0\n    assert plural(\"mnk\", 1) == 1\n    assert plural(\"mnk\", 12) == 2\n\n    assert plural(\"mt\", 1) == 0\n    assert plural(\"mt\", 0) == 1\n    assert plural(\"mt\", 119) == 2\n    assert plural(\"mt\", 67) == 3\n\n    assert plural(\"pl\", 1) == 0\n    assert plural(\"pl\", 102) == 1\n    assert plural(\"pl\", 713) == 2\n\n    assert plural(\"ro\", 1) == 0\n    assert plural(\"ro\", 19) == 1\n    assert plural(\"ro\", 80) == 2\n\n    assert plural(\"sl\", 320) == 0\n    assert plural(\"sl\", 101) == 1\n    assert plural(\"sl\", 202) == 2\n    assert plural(\"sl\", 303) == 3\n  end\nend\n"
  },
  {
    "path": "test/gettext_test.exs",
    "content": "defmodule GettextTest.TranslatorWithDuckInterpolator.Interpolator do\n  @behaviour Gettext.Interpolation\n\n  @impl Gettext.Interpolation\n  def runtime_interpolate(message, bindings),\n    do: {:ok, \"quack #{message} #{inspect(bindings)} quack\"}\n\n  @impl Gettext.Interpolation\n  defmacro compile_interpolate(_message_type, message, bindings) do\n    quote do\n      {:ok, \"quack #{unquote(message)} #{inspect(unquote(bindings))} quack\"}\n    end\n  end\n\n  @impl Gettext.Interpolation\n  def message_format, do: \"duck-format\"\nend\n\ndefmodule GettextTest.TranslatorWithDuckInterpolator do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    interpolation: GettextTest.TranslatorWithDuckInterpolator.Interpolator,\n    priv: \"test/fixtures/single_messages\"\nend\n\ndefmodule GettextTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureIO\n  import ExUnit.CaptureLog\n\n  alias GettextTest.Backend\n\n  describe \"get_locale/0,1\" do\n    test \"returns \\\"en\\\" as the default\" do\n      assert Gettext.get_locale() == \"en\"\n      assert Gettext.get_locale(Backend) == \"en\"\n    end\n\n    test \"gets the locale set by put_locale/2\" do\n      # First, we set the local for just one backend:\n      Gettext.put_locale(Backend, \"pt_BR\")\n\n      # Now, let's check that only that backend was affected.\n      assert Gettext.get_locale(Backend) == \"pt_BR\"\n      assert Gettext.get_locale() == \"en\"\n\n      # Now, let's change the global locale:\n      Gettext.put_locale(\"it\")\n\n      # Let's check that the global locale was affected and that get_locale/1\n      # returns the global locale, but only for backends that have no\n      # backend-specific locale set.\n      assert Gettext.get_locale() == \"it\"\n      assert Gettext.get_locale(Backend) == \"pt_BR\"\n    end\n\n    test \"uses the default locale of the :gettext application\" do\n      global_default = Application.get_env(:gettext, :default_locale)\n\n      try do\n        Application.put_env(:gettext, :default_locale, \"fr\")\n        assert Gettext.get_locale() == \"fr\"\n        assert Gettext.get_locale(Backend) == \"fr\"\n      after\n        Application.put_env(:gettext, :default_locale, global_default)\n      end\n    end\n  end\n\n  describe \"put_locale/2\" do\n    test \"only accepts binaries\" do\n      msg = \"put_locale/2 only accepts binary locales, got: :en\"\n\n      assert_raise ArgumentError, msg, fn ->\n        Gettext.put_locale(Backend, :en)\n      end\n    end\n  end\n\n  describe \"put_locale!/2\" do\n    test \"raises an error when unsupported locale is passed as argument\" do\n      msg = \"put_locale!/2 only support known locales, got: \\\"kr\\\"\"\n      assert Gettext.known_locales(Backend) == [\"it\", \"ja\"]\n\n      assert_raise ArgumentError, msg, fn ->\n        Gettext.put_locale!(Backend, \"kr\")\n      end\n    end\n  end\n\n  describe \"with_locale/3\" do\n    test \"doesn't raise if no locale was set (defaulting to 'en')\" do\n      Process.delete(Backend)\n\n      Gettext.with_locale(Backend, \"it\", fn ->\n        assert Gettext.gettext(Backend, \"Hello world\") == \"Ciao mondo\"\n      end)\n\n      assert Gettext.get_locale(Backend) == \"en\"\n    end\n\n    test \"runs a function with a given locale and returns the returned value\" do\n      Gettext.put_locale(Backend, \"fr\")\n      # no 'fr' message\n      assert Gettext.gettext(Backend, \"Hello world\") == \"Hello world\"\n\n      res =\n        Gettext.with_locale(Backend, \"it\", fn ->\n          assert Gettext.gettext(Backend, \"Hello world\") == \"Ciao mondo\"\n          :foo\n        end)\n\n      assert Gettext.get_locale(Backend) == \"fr\"\n      assert res == :foo\n    end\n\n    test \"resets the locale even if the given function raises\" do\n      Gettext.put_locale(Backend, \"fr\")\n\n      assert_raise RuntimeError, fn ->\n        Gettext.with_locale(Backend, \"it\", fn -> raise \"foo\" end)\n      end\n\n      assert Gettext.get_locale(Backend) == \"fr\"\n\n      catch_throw(Gettext.with_locale(Backend, \"it\", fn -> throw(:foo) end))\n      assert Gettext.get_locale(Backend) == \"fr\"\n    end\n  end\n\n  describe \"known_locales/1\" do\n    test \"returns all the locales for which a backend has PO files\" do\n      assert Gettext.known_locales(Backend) == [\"it\", \"ja\"]\n      assert Gettext.known_locales(GettextTest.BackendWithAllowedLocalesAtom) == [\"es\"]\n      assert Gettext.known_locales(GettextTest.BackendWithAllowedLocalesString) == [\"es\"]\n    end\n  end\n\n  test \"a custom default_domain can be set for a backend\" do\n    Code.eval_quoted(\n      quote do\n        defmodule DefaultDomainTest do\n          use Gettext, backend: GettextTest.BackendWithDefaultDomain\n\n          def test(\"Invalid email address\"), do: gettext(\"Invalid email address\")\n          def test(\"Hello world\"), do: gettext(\"Hello world\")\n        end\n      end\n    )\n\n    Gettext.put_locale(\"it\")\n\n    assert apply(DefaultDomainTest, :test, [\"Invalid email address\"]) ==\n             \"Indirizzo email non valido\"\n\n    assert apply(DefaultDomainTest, :test, [\"Hello world\"]) == \"Hello world\"\n  end\n\n  test \"MissingBindingsError log messages\" do\n    assert capture_log(fn ->\n             Gettext.pgettext(Backend, \"test\", \"Hello %{name}, missing message!\", %{})\n           end) =~\n             \"missing Gettext bindings: [:name] (backend GettextTest.Backend,\" <>\n               \" locale \\\"en\\\", domain \\\"default\\\", msgctxt \\\"test\\\", msgid \\\"Hello \" <>\n               \"%{name}, missing message!\\\")\"\n  end\n\n  describe \"*gettext functions (singular)\" do\n    setup do\n      Gettext.put_locale(Backend, \"it\")\n      :ok\n    end\n\n    test \"gettext/2\" do\n      assert Gettext.gettext(Backend, \"Hello world\") == \"Ciao mondo\"\n      assert Gettext.gettext(Backend, \"Nonexistent\") == \"Nonexistent\"\n    end\n\n    test \"dgettext/4\" do\n      msgid = \"Invalid email address\"\n      assert Gettext.dgettext(Backend, \"errors\", msgid) == \"Indirizzo email non valido\"\n\n      assert Gettext.dgettext(Backend, \"foo\", \"Foo\") == \"Foo\"\n\n      log =\n        capture_log(fn ->\n          assert Gettext.dgettext(Backend, \"interpolations\", \"Hello %{name}\", %{}) ==\n                   \"Ciao %{name}\"\n        end)\n\n      assert log =~ \"[error] missing Gettext bindings: [:name]\"\n    end\n\n    test \"pgettext/3\" do\n      assert Gettext.pgettext(Backend, \"test\", \"Hello world\") == \"Ciao mondo\"\n      assert Gettext.pgettext(Backend, \"test\", \"Nonexistent\") == \"Nonexistent\"\n    end\n\n    test \"dpgettext/4\" do\n      assert Gettext.dpgettext(Backend, \"default\", \"test\", \"Hello world\") ==\n               \"Ciao mondo\"\n    end\n  end\n\n  describe \"*ngettext functions (plural)\" do\n    setup do\n      Gettext.put_locale(Backend, \"it\")\n      :ok\n    end\n\n    test \"ngettext/5\" do\n      msgid = \"One cake, %{name}\"\n      msgid_plural = \"%{count} cakes, %{name}\"\n      assert Gettext.ngettext(Backend, msgid, msgid_plural, 1, %{name: \"Meg\"}) == \"One cake, Meg\"\n      assert Gettext.ngettext(Backend, msgid, msgid_plural, 5, %{name: \"Meg\"}) == \"5 cakes, Meg\"\n    end\n\n    test \"dngettext/6\" do\n      msgid = \"You have one message, %{name}\"\n      msgid_plural = \"You have %{count} messages, %{name}\"\n\n      assert Gettext.dngettext(Backend, \"interpolations\", msgid, msgid_plural, 1, %{name: \"Meg\"}) ==\n               \"Hai un messaggio, Meg\"\n\n      assert Gettext.dngettext(Backend, \"interpolations\", msgid, msgid_plural, 5, %{name: \"Meg\"}) ==\n               \"Hai 5 messaggi, Meg\"\n\n      assert Gettext.dngettext(Backend, \"interpolations\", \"Month\", \"%{count} months\", 5) ==\n               \"5 mesi\"\n    end\n\n    test \"pngettext/6\" do\n      msgctxt = \"test\"\n      msgid = \"One cake, %{name}\"\n      msgid_plural = \"%{count} cakes, %{name}\"\n\n      assert Gettext.pngettext(Backend, msgctxt, msgid, msgid_plural, 1, %{name: \"Meg\"}) ==\n               \"One cake, Meg\"\n\n      assert Gettext.pngettext(Backend, msgctxt, msgid, msgid_plural, 5, %{name: \"Meg\"}) ==\n               \"5 cakes, Meg\"\n    end\n\n    test \"dpngettext/6\" do\n      msgid = \"You have one message, %{name}\"\n      msgid_plural = \"You have %{count} messages, %{name}\"\n\n      assert Gettext.dpngettext(Backend, \"interpolations\", \"test\", msgid, msgid_plural, 1, %{\n               name: \"Meg\"\n             }) ==\n               \"Hai un messaggio, Meg\"\n\n      assert Gettext.dpngettext(Backend, \"interpolations\", \"test\", msgid, msgid_plural, 5, %{\n               name: \"Meg\"\n             }) ==\n               \"Hai 5 messaggi, Meg\"\n\n      assert Gettext.dpngettext(\n               Backend,\n               \"default\",\n               \"test\",\n               \"One new email\",\n               \"%{count} new emails\",\n               5,\n               %{name: \"Meg\"}\n             ) == \"5 nuove test email\"\n    end\n  end\n\n  test \"the d?n?gettext functions support kw list for interpolations\" do\n    Gettext.put_locale(Backend, \"it\")\n    assert Gettext.gettext(Backend, \"Hello %{name}\", name: \"José\") == \"Hello José\"\n  end\n\n  test \"uses custom interpolator\" do\n    assert Gettext.gettext(GettextTest.TranslatorWithDuckInterpolator, \"foo\") ==\n             \"quack foo %{} quack\"\n  end\n\n  test \"use Gettext for defining backends is deprecated\" do\n    stderr =\n      capture_io(:stderr, fn ->\n        Code.eval_quoted(\n          quote do\n            defmodule DeprecatedWayOfDefiningBackend do\n              use Gettext, otp_app: :my_app\n            end\n          end\n        )\n      end)\n\n    expected_message = \"\"\"\n    Defining a Gettext backend by calling:\n\n        use Gettext, otp_app: :my_app\n\n    is deprecated. To define a backend, call:\n\n        use Gettext.Backend, otp_app: :my_app\n\n    Then, replace importing your backend:\n\n        import DeprecatedWayOfDefiningBackend\n\n    with calling this in your module:\n\n        use Gettext, backend: DeprecatedWayOfDefiningBackend\n\n      nofile:1: DeprecatedWayOfDefiningBackend (module)\n\n    \"\"\"\n\n    assert stderr =~ expected_message\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/gettext.extract_test.exs",
    "content": "defmodule Mix.Tasks.Gettext.ExtractTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureIO\n  import GettextTest.MixProjectHelpers\n\n  @moduletag :tmp_dir\n\n  setup_all do\n    # To suppress the `redefining module MyApp` warnings for the test modules\n    Code.compiler_options(ignore_module_conflict: true)\n    :ok\n  end\n\n  test \"extracting and extracting with --merge\", %{test: test, tmp_dir: tmp_dir} = context do\n    create_test_mix_file(context)\n\n    write_file(context, \"lib/my_app.ex\", \"\"\"\n    defmodule MyApp.Gettext do\n      use Gettext.Backend, otp_app: #{inspect(test)}\n    end\n\n    defmodule MyApp do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: gettext(\"hello\")\n    end\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        in_project(test, tmp_dir, fn _module -> run([]) end)\n      end)\n\n    assert output =~ \"Extracted priv/gettext/default.pot\"\n\n    assert read_file(context, \"priv/gettext/default.pot\") =~ \"\"\"\n           #: lib/my_app.ex:7\n           #, elixir-autogen, elixir-format\n           msgid \"hello\"\n           msgstr \"\"\n           \"\"\"\n\n    # Test --merge too.\n\n    write_file(context, \"lib/other.ex\", \"\"\"\n    defmodule MyApp.Other do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: dgettext(\"my_domain\", \"other\")\n    end\n    \"\"\")\n\n    write_file(context, \"priv/gettext/it/LC_MESSAGES/my_domain.po\", \"\")\n\n    capture_io(fn ->\n      in_project(test, tmp_dir, fn _module -> run([\"--merge\"]) end)\n    end)\n\n    assert read_file(context, \"priv/gettext/it/LC_MESSAGES/my_domain.po\") == \"\"\"\n           #: lib/other.ex:3\n           #, elixir-autogen, elixir-format\n           msgid \"other\"\n           msgstr \"\"\n           \"\"\"\n\n    capture_io(fn ->\n      in_project(test, tmp_dir, fn _module -> run([\"--merge\"]) end)\n    end) =~ \"Wrote priv/gettext/it/LC_MESSAGES/my_domain.po\"\n  end\n\n  test \"--check-up-to-date should fail if no POT files have been created\",\n       %{test: test, tmp_dir: tmp_dir} = context do\n    create_test_mix_file(context)\n\n    write_file(context, \"lib/my_app.ex\", \"\"\"\n    defmodule MyApp.Gettext do\n      use Gettext.Backend, otp_app: #{inspect(test)}\n    end\n\n    defmodule MyApp do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: gettext(\"hello\")\n    end\n    \"\"\")\n\n    write_file(context, \"lib/other.ex\", \"\"\"\n    defmodule MyApp.Other do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: dgettext(\"my_domain\", \"other\")\n    end\n    \"\"\")\n\n    expected_message = \"\"\"\n    mix gettext.extract failed due to --check-up-to-date.\n    The following POT files were not extracted or are out of date:\n\n      * priv/gettext/default.pot\n      * priv/gettext/my_domain.pot\n    \"\"\"\n\n    capture_io(fn ->\n      assert_raise Mix.Error, expected_message, fn ->\n        in_project(test, tmp_dir, fn _module ->\n          run([\"--check-up-to-date\"])\n        end)\n      end\n    end)\n  end\n\n  test \"--check-up-to-date should pass if nothing changed\",\n       %{test: test, tmp_dir: tmp_dir} = context do\n    create_test_mix_file(context, write_reference_comments: false)\n\n    write_file(context, \"lib/my_app.ex\", \"\"\"\n    defmodule MyApp.Gettext do\n      use Gettext.Backend, otp_app: #{inspect(test)}\n    end\n\n    defmodule MyApp do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: gettext(\"hello\")\n    end\n    \"\"\")\n\n    capture_io(fn ->\n      in_project(test, tmp_dir, fn _module ->\n        run([])\n      end)\n\n      in_project(test, tmp_dir, fn _module ->\n        run([\"--check-up-to-date\"])\n      end)\n    end)\n  end\n\n  test \"--check-up-to-date should fail if POT files are outdated\",\n       %{test: test, tmp_dir: tmp_dir} = context do\n    create_test_mix_file(context)\n\n    write_file(context, \"lib/my_app.ex\", \"\"\"\n    defmodule MyApp.Gettext do\n      use Gettext.Backend, otp_app: #{inspect(test)}\n    end\n\n    defmodule MyApp do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: gettext(\"hello\")\n    end\n    \"\"\")\n\n    write_file(context, \"lib/other.ex\", \"\"\"\n    defmodule MyApp.Other do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: dgettext(\"my_domain\", \"other\")\n    end\n    \"\"\")\n\n    capture_io(fn ->\n      in_project(test, tmp_dir, fn _module -> run([]) end)\n    end)\n\n    write_file(context, \"lib/my_app.ex\", \"\"\"\n    defmodule MyApp.Gettext do\n      use Gettext.Backend, otp_app: #{inspect(test)}\n    end\n\n    defmodule MyApp do\n      use Gettext, backend: MyApp.Gettext\n      def foo(), do: gettext(\"hello world\")\n    end\n    \"\"\")\n\n    expected_message = \"\"\"\n    mix gettext.extract failed due to --check-up-to-date.\n    The following POT files were not extracted or are out of date:\n\n      * priv/gettext/default.pot\n    \"\"\"\n\n    capture_io(fn ->\n      assert_raise Mix.Error, expected_message, fn ->\n        in_project(test, tmp_dir, fn _module ->\n          run([\"--check-up-to-date\"])\n        end)\n      end\n    end)\n  end\n\n  defp run(args) do\n    Mix.Tasks.Gettext.Extract.run(args)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/gettext.merge_test.exs",
    "content": "defmodule Mix.Tasks.Gettext.MergeTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureIO\n\n  test \"raises an error when if one of the files doesn't exist\" do\n    assert_raise Mix.Error, \"No such file: foo.po\", fn ->\n      run(~w(foo.po bar.pot))\n    end\n  end\n\n  test \"raises an error if the files aren't a .po file and a .pot file\" do\n    assert_raise Mix.Error, \"Arguments must be a PO file and a PO/POT file\", fn ->\n      run(~w(foo.ex bar.exs))\n    end\n  end\n\n  test \"passing more than one argument raises an error\" do\n    assert_raise Mix.Error, ~r/^You can only pass one or two arguments/, fn ->\n      run(~w(foo bar baz bong))\n    end\n  end\n\n  test \"passing no arguments raises an error\" do\n    assert_raise Mix.Error, ~r/You can only pass one or two arguments/, fn ->\n      run([])\n    end\n  end\n\n  @tag :tmp_dir\n  test \"passing a :fuzzy_threshold outside of 0..1 raises an error\", %{tmp_dir: tmp_dir} do\n    File.mkdir_p(tmp_dir)\n\n    assert_raise Mix.Error, \"The :fuzzy_threshold option must be a float >= 0.0 and <= 1.0\", fn ->\n      run([tmp_dir, \"--fuzzy-threshold\", \"5.0\"])\n    end\n  end\n\n  @tag :tmp_dir\n  test \"merging an existing PO file with a new POT file\", %{tmp_dir: tmp_dir} do\n    pot_contents = \"\"\"\n    msgid \"hello\"\n    msgstr \"\"\n    \"\"\"\n\n    write_file(Path.join(tmp_dir, \"foo.pot\"), pot_contents)\n\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), \"\")\n\n    output =\n      capture_io(fn ->\n        run([Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), Path.join(tmp_dir, \"foo.pot\")])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/foo\\.po}\n\n    assert output =~\n             \"(1 new message, 0 removed, 0 unchanged, 0 reworded (fuzzy), 0 marked as obsolete)\"\n\n    # The POT file is left unchanged\n    assert File.read!(Path.join(tmp_dir, \"foo.pot\")) == pot_contents\n\n    assert File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\")) == \"\"\"\n           msgid \"hello\"\n           msgstr \"\"\n           \"\"\"\n  end\n\n  @tag :tmp_dir\n  test \"marks messages as obsolete\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"foo.pot\"), \"\")\n\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), \"\"\"\n    msgid \"foo\"\n    msgstr \"\"\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        run([\n          Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"),\n          Path.join(tmp_dir, \"foo.pot\"),\n          \"--on-obsolete\",\n          \"mark_as_obsolete\"\n        ])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/foo.po}\n\n    assert output =~\n             \"(0 new messages, 0 removed, 0 unchanged, 0 reworded (fuzzy), 1 marked as obsolete)\"\n  end\n\n  @tag :tmp_dir\n  test \"removes obsolete messages\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"foo.pot\"), \"\")\n\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), \"\"\"\n    msgid \"foo\"\n    msgstr \"\"\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        run([\n          Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"),\n          Path.join(tmp_dir, \"foo.pot\"),\n          \"--on-obsolete\",\n          \"delete\"\n        ])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/foo.po}\n\n    assert output =~\n             \"(0 new messages, 1 removed, 0 unchanged, 0 reworded (fuzzy), 0 marked as obsolete)\"\n  end\n\n  @tag :tmp_dir\n  test \"validates on-obsolete\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"foo.pot\"), \"\")\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), \"\")\n\n    expected_message = \"\"\"\n    An invalid value was provided for the option `on_obsolete`.\n    Value: \"invalid\"\n    Valid Choices: \"delete\" / \"mark_as_obsolete\"\n    \"\"\"\n\n    assert_raise Mix.Error, expected_message, fn ->\n      run([\n        Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"),\n        Path.join(tmp_dir, \"foo.pot\"),\n        \"--on-obsolete\",\n        \"invalid\"\n      ])\n    end\n  end\n\n  @tag :tmp_dir\n  test \"passing a dir and a --locale opt will update/create PO files in the locale dir\",\n       %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"default.pot\"), \"\"\"\n    msgid \"def\"\n    msgstr \"\"\n    \"\"\")\n\n    write_file(Path.join(tmp_dir, \"new.pot\"), \"\"\"\n    msgid \"new\"\n    msgstr \"\"\n    \"\"\")\n\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/default.po\"), \"\")\n\n    output =\n      capture_io(fn ->\n        run([tmp_dir, \"--locale\", \"it\"])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/new.po}\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/default.po}\n\n    assert File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/default.po\")) == \"\"\"\n           msgid \"def\"\n           msgstr \"\"\n           \"\"\"\n\n    new_po = File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/new.po\"))\n\n    assert new_po =~ ~S\"\"\"\n           msgid \"\"\n           msgstr \"\"\n           \"Language: it\\n\"\n           \"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n           msgid \"new\"\n           msgstr \"\"\n           \"\"\"\n\n    assert String.starts_with?(new_po, \"## \\\"msgid\\\"s in this file come from POT\")\n  end\n\n  @tag :tmp_dir\n  test \"enabling --store-previous-message-on-fuzzy-match stores previous message\",\n       %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"default.pot\"), \"\"\"\n    msgid \"Hello Worlds\"\n    msgstr \"\"\n    \"\"\")\n\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/default.po\"), \"\"\"\n    msgid \"Hello World\"\n    msgstr \"\"\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        run([tmp_dir, \"--locale\", \"it\", \"--store-previous-message-on-fuzzy-match\"])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/default.po}\n\n    assert File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/default.po\")) == \"\"\"\n           #, fuzzy\n           #| msgid \"Hello World\"\n           msgid \"Hello Worlds\"\n           msgstr \"\"\n           \"\"\"\n  end\n\n  @tag :tmp_dir\n  test \"passing a dir and a --locale opt will update/create PO files in the locale dir with custom plural forms\",\n       %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"new.pot\"), \"\"\"\n    msgid \"new\"\n    msgstr \"\"\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        run([\n          tmp_dir,\n          \"--locale\",\n          \"it\",\n          \"--plural-forms-header\",\n          \"nplurals=3; plural=n==0 ? 0 : n > 1;\"\n        ])\n      end)\n\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/new.po}\n\n    assert File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/new.po\")) =~ ~S\"\"\"\n           msgid \"\"\n           msgstr \"\"\n           \"Language: it\\n\"\n           \"Plural-Forms: nplurals=3; plural=n==0 ? 0 : n > 1;\\n\"\n\n           msgid \"new\"\n           msgstr \"\"\n           \"\"\"\n  end\n\n  @tag :tmp_dir\n  test \"passing a dir and a --locale opt will update/create PO files in the locale dir with app env plural forms\",\n       %{tmp_dir: tmp_dir} do\n    Application.put_env(:gettext, :plural_forms, GettextTest.CustomPlural)\n\n    write_file(Path.join(tmp_dir, \"new.pot\"), \"\"\"\n    msgid \"new\"\n    msgstr \"\"\n    \"\"\")\n\n    output =\n      capture_io(fn ->\n        run([tmp_dir, \"--locale\", \"elv\"])\n      end)\n\n    assert output =~ ~r{Wrote .*/elv/LC_MESSAGES/new.po}\n\n    assert File.read!(Path.join(tmp_dir, \"elv/LC_MESSAGES/new.po\")) =~ ~S\"\"\"\n           msgid \"\"\n           msgstr \"\"\n           \"Language: elv\\n\"\n           \"Plural-Forms: nplurals=2\\n\"\n\n           msgid \"new\"\n           msgstr \"\"\n           \"\"\"\n  after\n    Application.put_env(:gettext, :plural_forms, Gettext.Plural)\n  end\n\n  @tag :tmp_dir\n  test \"passing just a dir merges with PO files in every locale\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"fr/LC_MESSAGES/foo.po\"), \"\")\n    write_file(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\"), \"\")\n\n    contents = \"\"\"\n    msgid \"foo\"\n    msgstr \"\"\n    \"\"\"\n\n    write_file(Path.join(tmp_dir, \"foo.pot\"), contents)\n\n    output = capture_io(fn -> run([tmp_dir]) end)\n\n    assert output =~ ~r{Wrote .*/fr/LC_MESSAGES/foo.po}\n    assert output =~ ~r{Wrote .*/it/LC_MESSAGES/foo.po}\n\n    assert File.read!(Path.join(tmp_dir, \"fr/LC_MESSAGES/foo.po\")) == contents\n    assert File.read!(Path.join(tmp_dir, \"it/LC_MESSAGES/foo.po\")) == contents\n  end\n\n  @tag :tmp_dir\n  test \"non-existing locale/LC_MESSAGES directories are created\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"foo.pot\"), \"\"\"\n    msgid \"foo\"\n    msgstr \"\"\n    \"\"\")\n\n    created_dir = Path.join([tmp_dir, \"en\", \"LC_MESSAGES\"])\n\n    refute File.dir?(created_dir)\n\n    output =\n      capture_io(fn ->\n        run([tmp_dir, \"--locale\", \"en\"])\n      end)\n\n    assert File.dir?(created_dir)\n    assert output =~ \"Created directory #{created_dir}\"\n  end\n\n  @tag :tmp_dir\n  test \"informative comments at the top of the file\", %{tmp_dir: tmp_dir} do\n    write_file(Path.join(tmp_dir, \"inf.pot\"), \"\"\"\n    msgid \"foo\"\n    msgstr \"\"\n    \"\"\")\n\n    capture_io(:stdio, fn ->\n      capture_io(:stderr, fn ->\n        run([tmp_dir, \"--locale\", \"en\"])\n        contents = File.read!(Path.join(tmp_dir, \"en/LC_MESSAGES/inf.po\"))\n        assert contents =~ \"## \\\"msgid\\\"s in this file\"\n\n        # Running the task again without having change the PO file shouldn't\n        # remove the informative comment.\n        run([tmp_dir, \"--locale\", \"en\"])\n        assert contents == File.read!(Path.join(tmp_dir, \"en/LC_MESSAGES/inf.po\"))\n      end)\n    end)\n  end\n\n  defp write_file(path, contents) do\n    File.mkdir_p!(Path.dirname(path))\n    File.write!(path, contents)\n  end\n\n  defp run(args) do\n    Mix.Tasks.Gettext.Merge.run(args)\n  end\nend\n"
  },
  {
    "path": "test/support/mix_project_helpers.ex",
    "content": "defmodule GettextTest.MixProjectHelpers do\n  def create_test_mix_file(context, gettext_config \\\\ []) do\n    write_file(context, \"mix.exs\", \"\"\"\n    defmodule MyApp.MixProject do\n      use Mix.Project\n\n      def project() do\n        [app: #{inspect(context.test)}, version: \"0.1.0\", gettext: #{inspect(gettext_config)}]\n      end\n\n      def application() do\n        [extra_applications: [:logger, :gettext]]\n      end\n    end\n    \"\"\")\n  end\n\n  def write_file(context, path, contents) do\n    path = Path.join(context.tmp_dir, path)\n    File.mkdir_p!(Path.dirname(path))\n    File.write!(path, contents)\n  end\n\n  def read_file(context, path) do\n    context.tmp_dir |> Path.join(path) |> File.read!()\n  end\n\n  def in_project(module, dir, fun) do\n    Mix.Project.in_project(module, dir, [prune_code_paths: false], fun)\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "defmodule GettextTest.CustomPlural do\n  @behaviour Gettext.Plural\n  def nplurals(\"elv\"), do: 2\n  def nplurals(other), do: Gettext.Plural.nplurals(other)\n  # Opposite of Italian (where 1 is singular, everything else is plural)\n  def plural(\"it\", 1), do: 1\n  def plural(\"it\", _), do: 0\nend\n\ndefmodule GettextTest.CustomCompiledPlural do\n  @behaviour Gettext.Plural\n\n  @impl Gettext.Plural\n  def init(plural_info), do: plural_info\n\n  @impl Gettext.Plural\n  def nplurals(plural_info) do\n    send(self(), {:nplurals_context, plural_info})\n\n    plural_info\n    |> Gettext.Plural.init()\n    |> Gettext.Plural.nplurals()\n  end\n\n  @impl Gettext.Plural\n  def plural(plural_info, count) do\n    send(self(), {:plural_context, plural_info})\n\n    plural_info\n    |> Gettext.Plural.init()\n    |> Gettext.Plural.plural(count)\n  end\nend\n\ndefmodule GettextTest.Backend do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    priv: \"test/fixtures/single_messages\"\n\n  def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do\n    send(self(), {locale, domain, msgctxt, msgid, bindings})\n    super(locale, domain, msgctxt, msgid, bindings)\n  end\n\n  def handle_missing_plural_translation(\n        locale,\n        domain,\n        msgctxt,\n        msgid,\n        msgid_plural,\n        n,\n        bindings\n      ) do\n    send(self(), {locale, domain, msgctxt, msgid, msgid_plural, n, bindings})\n    super(locale, domain, msgctxt, msgid, msgid_plural, n, bindings)\n  end\nend\n\ndefmodule GettextTest.BackendWithAllowedLocalesString do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    priv: \"test/fixtures/multi_messages\",\n    allowed_locales: [\"es\"]\nend\n\ndefmodule GettextTest.BackendWithAllowedLocalesAtom do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    priv: \"test/fixtures/multi_messages\",\n    allowed_locales: [:es]\nend\n\ndefmodule GettextTest.BackendWithDefaultDomain do\n  use Gettext.Backend,\n    otp_app: :test_application,\n    priv: \"test/fixtures/single_messages\",\n    default_domain: \"errors\"\nend\n\nExUnit.start()\n"
  }
]