[
  {
    "path": ".github/workflows/bundle-audit.yml",
    "content": "name: Bundle Audit\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  rubocop:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 2.7\n    - name: Patch-level verification for Bundler\n      run: |\n        gem install bundle-audit\n        bundle-audit check --update\n"
  },
  {
    "path": ".github/workflows/rubocop.yml",
    "content": "name: Lint Ruby\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n\njobs:\n  rubocop:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 2.7\n    - name: Lint Ruby code with RuboCop\n      run: |\n        gem install rubocop\n        rubocop\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_JOBS: 4\n      BUNDLE_RETRY: 3\n      VERIFY_RESERVED: 1\n      CI: true\n      CUCUMBER_PUBLISH_QUIET: true\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby: [2.7, 2.6, 2.5, 2.4]\n        gemfile: [\n          'gemfiles/rails50.gemfile',\n          'gemfiles/rails51.gemfile',\n          'gemfiles/rails52.gemfile',\n          'gemfiles/rails60.gemfile',\n          'gemfiles/rails61.gemfile'\n        ]\n        exclude:\n        - ruby: 2.4\n          gemfile: gemfiles/rails52.gemfile\n        - ruby: 2.4\n          gemfile: gemfiles/rails60.gemfile\n        - ruby: 2.4\n          gemfile: gemfiles/rails61.gemfile\n\n\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions/cache@v1\n      with:\n        path: /home/runner/bundle\n        key: bundle-${{ matrix.ruby }}-${{ matrix.gemfile }}-${{ hashFiles(matrix.gemfile) }}-${{ hashFiles('**/*.gemspec') }}\n        restore-keys: |\n          bundle-${{ matrix.ruby }}-${{ matrix.gemfile }}-\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby }}\n    - name: Bundle install\n      run: |\n        bundle config path /home/runner/bundle\n        bundle config --global gemfile ${{ matrix.gemfile }}\n        bundle install\n        bundle update\n    - name: Run tests\n      run: bundle exec rake\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n\nGemfile.lock\nerl_crash.dump\n.ruby-version\n.vscode\nspec/dummy_app/log/\nvendor/\n.devcontainer/\n"
  },
  {
    "path": ".rspec",
    "content": "--format documentation\n--color\n--require spec_helper\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "AllCops:\n  # Include gemspec and Rakefile\n  Include:\n    - \"lib/**/*.rb\"\n    - \"lib/**/*.rake\"\n    - \"spec/**/*.rb\"\n  Exclude:\n    - \"bin/**/*\"\n    - \"lib/localer/ext/*.rb\"\n    - \"Appraisals\"\n    - \"Gemfile\"\n    - \"Rakefile\"\n    - \"*.gemspec\"\n    - \"spec/dummy_app/\"\n  DisplayCopNames: true\n  NewCops: enable\n  StyleGuideCopsOnly: false\n\nNaming/AccessorMethodName:\n  Enabled: false\n\nStyle/PercentLiteralDelimiters:\n  Enabled: false\n\nStyle/TrivialAccessors:\n  Enabled: false\n\nStyle/Documentation:\n  Exclude:\n    - \"spec/**/*.rb\"\n\nStyle/StringLiterals:\n  Enabled: false\n\nStyle/BlockDelimiters:\n  Exclude:\n    - \"spec/**/*.rb\"\n\nStyle/DoubleNegation:\n  Enabled: false\n\nStyle/HashEachMethods:\n  Enabled: true\n\nStyle/HashTransformKeys:\n  Enabled: true\n\nStyle/HashTransformValues:\n  Enabled: true\n\nLayout/SpaceInsideStringInterpolation:\n  EnforcedStyle: no_space\n\nLint/AmbiguousRegexpLiteral:\n  Enabled: false\n\nLint/AmbiguousBlockAssociation:\n  Enabled: false\n\nMetrics/MethodLength:\n  Exclude:\n    - \"spec/**/*.rb\"\n\nLayout/LineLength:\n  Max: 120\n  Exclude:\n    - \"spec/**/*.rb\"\n\nMetrics/BlockLength:\n  Exclude:\n    - \"spec/**/*.rb\"\n\nSecurity/YAMLLoad:\n  Enabled: false\n"
  },
  {
    "path": "Appraisals",
    "content": "appraise 'rails50' do\n  gem 'rails', '~> 5.0'\nend\n\nappraise 'rails51' do\n  gem 'rails', '~> 5.1'\nend\n\nappraise 'rails52' do\n  gem 'rails', '~> 5.2'\nend\n\nappraise 'rails60' do\n  gem 'rails', '~> 6.0'\nend\n\nappraise 'rails61' do\n  gem 'rails', '~> 6.1'\nend\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at deriabin@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngit_source(:github) {|repo_name| \"https://github.com/#{repo_name}\" }\n\n# Specify your gem's dependencies in localer.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Andrey Deryabin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n<p align=\"center\">\n<img title=\"Localer logo\" width=\"384\" height=\"100\" src=\"https://gist.githubusercontent.com/aderyabin/cb0512cbcd6cb4c79a4d84a4831109a5/raw/localer-logo.png\">\n</p>\n\n[![Gem Version](https://badge.fury.io/rb/localer.svg)](https://rubygems.org/gems/localer) [![Build Status](https://github.com/aderyabin/localer/workflows/Build/badge.svg)](https://github.com/aderyabin/localer/actions)\n\nLocaler is a tool that automatically detects missing I18n translations.\n\nThe goal is to preserve the integrity of translations. Localer parses and merges all  application locales’ keys. At the next step, it searches for missing translations among the calculated keys.\n\n<p align=\"left\">\n  <img height=\"500\" src=\"https://gist.githubusercontent.com/aderyabin/cb0512cbcd6cb4c79a4d84a4831109a5/raw/localer2.png\">\n</p>\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'localer'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install localer\n\n## Usage\n\nAt the root directory of a Rails app, run:\n\n    $ localer check .\n\nor for specific Rails path:\n\n    $ localer check /path/to/rails/application\n\n## CI integration\n\nLocaler is easy to integrate into your favorite CI workflow:\n```yml\n# .travis.yml\n\n# other configuration options\nscript:\n  - bundle exec bundle-audit\n  - bundle exec rubocop\n  - bundle exec rspec\n  - bundle exec localer\n```\n\nor\n\n```ruby\n# Rakefile\n\n# other requirements\nrequire 'localer/rake_task'\nLocaler::RakeTask.new()\n\ntask(:default).clear\ntask default: [:rubocop, :spec, :localer]\n```\n\n## Support\n\nLocaler supports\n* Ruby: 2.4, 2.5, 2.6, 2.7\n* Rails: 5.0, 5.1, 5.2, 6.0, 6.1\n\n## Configuration\n\nThe behavior of Localer can be controlled via the `.localer.yml` configuration file. It makes it possible to disable locales and keys. The file can be placed in your project directory.\n\n#### Disable specific locale\n\nBy default, Localer enables all locales, but you can disable it:\n\n```yml\nLocale:\n  EN:\n    Enabled: false\n```\n\n#### Exclude keys globally\nBy default, Localer enables all keys, but you can disable keys started with specified string or by regex:\n\n```yml\nExclude:\n  - /population\\z/\n  - .countries.france\n```\n\n#### Exclude keys for specific locale\n```yml\nLocale:\n  EN:\n    Exclude:\n      - /population\\z/\n      - .countries.france\n```\n\n## Using Rake\n\nLocaler ships with a rake task. To use Localer's rake task you simply need to require the task file and define a task with it. Below is a rake task that will run `localer`:\n\n```ruby\nrequire 'rubygems'\nrequire 'localer'\nrequire 'localer/rake_task'\n\nLocaler::RakeTask.new(:localer)\n```\n\nWhen you now run:\n\n    $ rake -T\n\nyou should see\n\n```\nrake localer  # Run Localer\n```\n\n## Development\n\nAfter checking out the repo, run `bundle exec appraisal install` to install dependencies for each appraisal. Then, run `bundle exec appraisal rake` to run the tests.\n\n## Built With\n\n* [Thor](https://github.com/erikhuda/thor) - Used for building  command-line interfaces.\n* [Appraisal](https://github.com/thoughtbot/appraisal) -  Used for testing against different versions of dependencies\n* [Cucumber](https://github.com/cucumber/cucumber) + [Aruba](https://github.com/cucumber/aruba) - Used for testing command-line commands\n\n## Acknowledge\nSpecial thanks to [Roman Shamin](https://www.facebook.com/romanshamin) for the logo.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/aderyabin/localer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Localer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/aderyabin/localer/blob/master/CODE_OF_CONDUCT.md).\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"bundler/gem_tasks\"\nrequire \"rubocop/rake_task\"\nrequire 'cucumber/rake/task'\n\nRuboCop::RakeTask.new\n\nCucumber::Rake::Task.new(:features) do |t|\n  t.cucumber_opts = \"features --format pretty\"\nend\n\ntask :default do\n  if ENV[\"RUBOCOP\"]\n    Rake::Task[\"rubocop\"].invoke\n  else\n    Rake::Task[\"features\"].invoke\n  end\nend\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-leap-day"
  },
  {
    "path": "bin/localer",
    "content": "#!/usr/bin/env ruby\n\nrequire \"thor\"\nrequire \"irb\"\nrequire_relative \"../lib/localer\"\n\nmodule Localer\n  class CLI < Thor\n    desc \"version\", \"Print Localer version\"\n    def version\n      say Localer::VERSION\n    end\n\n    desc \"check [/path/to/rails/application]\", \"Check missing translations\"\n    def check(app_path = Localer::Config::APP_PATH)\n      Localer.configure(options.dup.merge(app_path: app_path))\n\n      connect_to_rails\n\n      if Localer.data.complete?\n        say \"\\xE2\\x9C\\x94 No missing translations found.\", :green\n      else\n        missing_translations = Localer.data.missing_translations\n        say \"\\xE2\\x9C\\x96 Missing translations found (#{missing_translations.count}):\", :red\n        missing_translations.each do |tr|\n          say \"* #{tr}\"\n        end\n\n        exit 1\n      end\n    end\n\n    default_task :check\n\n    private\n\n    def connect_to_rails\n      return if Localer::Rails.connect!\n      say \"No Rails application found\"\n      exit 1\n    end\n  end\nend\n\n\nLocaler::CLI.start(ARGV)\n"
  },
  {
    "path": "features/global_exclude.feature",
    "content": "Feature: Localer\nScenario: Exclude strings\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Exclude:\n    - .countries.france\n    - .population\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Exclude regexp\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Exclude:\n    - /population\\z/\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Exclude strict regexp\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Exclude:\n    - /^.population/\n  \"\"\"\n  When I run checker\n  Then the checker should returns 1 missing translations:\n    | en.countries.france.population |\n"
  },
  {
    "path": "features/locale_option.feature",
    "content": "Feature: Localer\nScenario: Disable en locale\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Locale:\n    en:\n      Enabled: false\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Disable case-insensitive EN locale\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Locale:\n    EN:\n      Enabled: false\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Disable en.population\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  Locale:\n    en:\n      Exclude:\n        - .population.italy\n  \"\"\"\n  When I run checker\n  Then the checker should returns 2 missing translations:\n   | en.countries.france.population |\n   | en.population.france |\n\nScenario: With empty config file\n  Given a real locales\n  Given a config file with:\n  \"\"\"\n  \"\"\"\n  When I run checker\n  Then the checker should returns 4 missing translations:\n   | ru.population.italy |\n   | us.population.italy |\n   | en.countries.france.population |\n   | en.population.france |\n"
  },
  {
    "path": "features/missing_app.feature",
    "content": "Feature: Localer\n\nScenario: No rails application\n  When I run `localer check`\n  Then the checker should not found rails application\n\nScenario: No rails application at existed paths\n  When I run `localer check`\n  Then the checker should not found rails application\n\nScenario: No rails application at not non-existed\n  When I run `localer check non-existed_path`\n  Then the checker should not found rails application\n"
  },
  {
    "path": "features/real_locales.feature",
    "content": "Feature: Localer\n\nScenario: Real locales does not pass\n  Given a real locales\n  When I run checker\n  Then the checker should returns 4 missing translations:\n    | ru.population.italy |\n    | us.population.italy |\n    | en.population.france |\n    | en.countries.france.population |"
  },
  {
    "path": "features/simple.feature",
    "content": "Feature: Localer\n\nScenario: No locales files\n  When I run checker\n  Then the checker should pass\n\nScenario: Empty en locale\n  Given a \"en\" locale file with:\n  \"\"\"\n  en:\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Empty ru locale\n  Given a \"ru\" locale file with:\n  \"\"\"\n  ru:\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Complete locales\n  Given a \"en\" locale file with:\n  \"\"\"\n  en:\n    one: one\n  \"\"\"\n  Given a \"ru\" locale file with:\n  \"\"\"\n  ru:\n    one: один\n  \"\"\"\n  Given a \"us\" locale file with:\n  \"\"\"\n  us:\n    one: one\n  \"\"\"\n  When I run checker\n  Then the checker should pass\n\nScenario: Empty en locale\n  Given a \"ru\" locale file with:\n  \"\"\"\n  ru:\n    one: один\n  \"\"\"\n  When I run checker\n  Then the checker should fail\n\nScenario: Incorrect structure\n  Given a \"en\" locale file with:\n  \"\"\"\n  en:\n    too_long: \"Too Long\"\n  \"\"\"\n  Given a \"ru\" locale file with:\n  \"\"\"\n  ru:\n    too_long:\n      one: слишком большой длины (не может быть больше чем %{count} символ)\n      other: слишком большой длины (не может быть больше чем %{count} символа)\n  \"\"\"\n  When I run checker\n  Then the checker should fail\n"
  },
  {
    "path": "features/step_definitions/additional_cli_steps.rb",
    "content": "# frozen_string_literal: true\n\nGiven /^a \"(.*)\" locale file with:$/ do |locale, file_content|\n  write_file(\"#{LOCALE_DIR}/#{locale}.yml\", file_content)\nend\n\nGiven /^a config file with:$/ do |file_content|\n  write_file(CONFIG_PATH, file_content)\nend\n\nGiven /^a real locales$/ do # rubocop:disable Metrics/BlockLength\n  steps %{\n    Given a \"en\" locale file with:\n    \"\"\"\n    en:\n      population:\n        italy: 60.6\n      countries:\n        italy:\n          city: Rome\n        spain:\n          city: Madrid\n        france:\n          city: Paris\n    \"\"\"\n    Given a \"ru\" locale file with:\n    \"\"\"\n    ru:\n      population:\n        france: 66.9\n      countries:\n        italy:\n          city: Рим\n        spain:\n          city: Мадрид\n        france:\n          city: Париж\n          population: 66.9\n    \"\"\"\n    Given a \"us\" locale file with:\n    \"\"\"\n    us:\n      population:\n        france: 66.9\n      countries:\n        italy:\n          city: Rome\n        spain:\n          city: Madrid\n        france:\n          city: Paris\n          population: 66.9\n    \"\"\"\n  }\nend\n\nThen /^the checker should pass$/ do\n  step 'the output should contain \"✔ No missing translations found\"'\n  step 'the exit status should be 0'\nend\n\nThen /^the checker should fail$/ do\n  step 'the output should contain \"✖ Missing translations found\"'\n  step 'the exit status should be 1'\nend\n\nThen /^the checker should returns (.*) missing translations:$/ do |int, translations|\n  step %{the output should contain \"✖ Missing translations found (#{int})\"}\n  translations.raw.each do |tr|\n    step %{the output should match /^#{Regexp.escape('* ' + tr[0])}$/}\n  end\n\n  step 'the exit status should be 1'\nend\n\nThen /^the checker should not found rails application$/ do\n  step 'the output should contain \"No Rails application found\"'\n  step 'the exit status should be 1'\nend\n\nWhen /^I run checker$/ do\n  if defined?(run)\n    run(\"localer check ../../spec/dummy_app\")\n  else\n    run_command(\"localer check ../../spec/dummy_app\")\n  end\nend\n"
  },
  {
    "path": "features/support/env.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'aruba/cucumber'\n\nDUMMY_APP_DIR = \"../../spec/dummy_app\"\nLOCALE_DIR = \"#{DUMMY_APP_DIR}/config/locales\"\nCONFIG_PATH = \"#{DUMMY_APP_DIR}/.localer.yml\"\n\nAfter do |_|\n  %w[ru en us].each do |locale|\n    path = \"#{LOCALE_DIR}/#{locale}.yml\"\n    remove(path) if exist?(path)\n  end\n\n  remove(CONFIG_PATH) if exist?(CONFIG_PATH)\nend\n"
  },
  {
    "path": "gemfiles/rails50.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rails\", \"~> 5.0\"\n\ngemspec path: \"../\"\n"
  },
  {
    "path": "gemfiles/rails51.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rails\", \"~> 5.1\"\n\ngemspec path: \"../\"\n"
  },
  {
    "path": "gemfiles/rails52.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rails\", \"~> 5.2\"\n\ngemspec path: \"../\"\n"
  },
  {
    "path": "gemfiles/rails60.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rails\", \"~> 6.0\"\n\ngemspec path: \"../\"\n"
  },
  {
    "path": "gemfiles/rails61.gemfile",
    "content": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rails\", \"~> 6.1\"\n\ngemspec path: \"../\"\n"
  },
  {
    "path": "lib/localer/config/locale.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  class Config\n    # Provide config for locale\n    class Locale\n      extend Dry::Initializer\n      option :exclude, default: -> { [] }\n      option :enabled, default: -> { true }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/config.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'yaml'\nrequire_relative '../localer/ext/hash'\nrequire_relative 'config/locale'\n\nmodule Localer # :nodoc:\n  using Localer::Ext::Hash\n\n  # Loads and parse Localer config file `.localer.yml`\n  class Config\n    extend Dry::Initializer\n\n    APP_PATH = Dir.pwd\n    CONFIG_FILENAME = \".localer.yml\"\n\n    option :exclude, default: -> { [] }\n    option :locale, proc { |hash| parse_locales(hash) }, default: -> { Hash.new(Locale.new) }\n    option :app_path, default: -> { APP_PATH }\n\n    class << self\n      def load(options = {})\n        opts = options.deep_symbolize_keys\n        app_path = opts.fetch(:app_path, APP_PATH)\n        file_options = file_config(CONFIG_FILENAME, app_path)\n        new(file_options.deep_merge(opts).deep_symbolize_keys)\n      end\n\n      def file_config(filename, path)\n        filename = File.expand_path(filename, path)\n        return {} unless File.exist?(filename)\n        return {} if File.zero?(filename)\n\n        YAML\n          .load_file(filename)\n          .deep_downcase_keys\n          .deep_symbolize_keys\n      end\n\n      def parse_locales(hash)\n        hash.each_with_object(Hash.new(Locale.new)) do |(l, v), h|\n          h[l] = Locale.new(v)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/data/checker.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  class Data\n    # Check missing translations\n    # Returns true if no missing translations found, otherwise false\n    class Checker < Service\n      param :data\n\n      def call\n        data.each do |_locale, _key, value|\n          return false if value.nil?\n        end\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/data/missing_translations.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  class Data\n    # A service that returns array of missing translations\n    class MissingTranslations < Service\n      param :data\n\n      def call\n        missing = []\n        data.each do |locale, key, value|\n          missing.push(\"#{locale}#{key}\") if value.nil?\n        end\n        missing\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/data/processor.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative '../ext/string'\nmodule Localer # :nodoc:\n  using Localer::Ext::String\n\n  class Data\n    # Parse translations into hash:\n    # key: translation key\n    # value: hash of locale values\n    class Processor < Service\n      param :translations\n      param :config, default: -> { Localer.config }\n\n      attr_reader :data, :locales\n\n      def call\n        @data = Hash.new { |hsh, key| hsh[key] = {} }\n        @locales = []\n        translations.each do |(locale, translation)|\n          next unless config.locale[locale.downcase].enabled\n\n          @locales.push locale\n          prepare(locale, translation)\n        end\n        [@locales, @data]\n      end\n\n      private\n\n      def prepare(locale, translation, prefix = \"\")\n        if translation.is_a?(Hash)\n          translation.each do |(key, value)|\n            full_key = prefix + \".#{key}\"\n            next if exclude?(full_key, locale)\n\n            prepare(locale, value, full_key)\n          end\n        else\n          # @data[prefix] ||= {}\n          @data[prefix][locale] = translation\n        end\n      end\n\n      def exclude?(key, locale)\n        (config.exclude + config.locale[locale.downcase].exclude).any? do |pattern|\n          match?(key, pattern)\n        end\n      end\n\n      def match?(key, pattern)\n        if (regex = pattern.to_regexp)\n          key =~ regex\n        else\n          key.start_with?(pattern)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/data/service.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'dry-initializer'\nmodule Localer\n  class Data\n    # Core service object\n    class Service\n      extend Dry::Initializer # use `param` and `option` for dependencies\n\n      class << self\n        # Instantiates and calls the service at once\n        def call(*args, &block)\n          new(*args).call(&block)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/data.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"data/service\"\nrequire_relative \"data/checker\"\nrequire_relative \"data/processor\"\nrequire_relative \"data/missing_translations\"\n\nmodule Localer\n  # Stores translations and provides\n  # check methods\n  class Data\n    extend Dry::Initializer\n    param :source, default: -> { {} }\n    param :config, default: -> { Localer.config }\n\n    attr_reader :translations, :locales\n\n    def initialize(*args)\n      super\n      @locales, @translations = Processor.call(source, config)\n    end\n\n    def complete?\n      Checker.call(self)\n    end\n\n    def missing_translations\n      MissingTranslations.call(self)\n    end\n\n    def each\n      @translations.each do |key, value|\n        @locales.each do |locale|\n          yield locale, key, value[locale]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/ext/hash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  module Ext\n    # Extend Hash through refinements\n    module Hash\n      refine ::Hash do\n        # From ActiveSupport http://api.rubyonrails.org/classes/Hash.html#metho\n        def deep_merge!(other_hash)\n          other_hash.each_pair do |current_key, other_value|\n            this_value = self[current_key]\n\n            if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)\n              this_value.deep_merge!(other_value)\n              this_value\n            else\n              self[current_key] = other_value\n            end\n          end\n\n          self\n        end\n\n        def deep_merge(other_hash, &block)\n          dup.deep_merge!(other_hash, &block)\n        end\n\n        def deep_symbolize_keys\n          deep_transform_keys do |key|\n            begin\n              key.to_sym\n            rescue StandardError\n              key\n            end\n          end\n        end\n\n        def deep_downcase_keys\n          deep_transform_keys do |key|\n            begin\n              key.downcase\n            rescue StandardError\n              key\n            end\n          end\n        end\n\n        def deep_transform_keys(&block)\n          _deep_transform_keys_in_object(self, &block)\n        end\n\n        def deep_transform_keys!(&block)\n          _deep_transform_keys_in_object!(self, &block)\n        end\n\n        private\n\n        def _deep_transform_keys_in_object!(object, &block)\n          case object\n          when ::Hash\n            object.keys.each do |key|\n              value = object.delete(key)\n              object[yield(key)] = _deep_transform_keys_in_object!(value, &block)\n            end\n            object\n          when Array\n            object.map! { |e| _deep_transform_keys_in_object!(e, &block) }\n          else\n            object\n          end\n        end\n\n        # support methods for deep transforming nested hashes and arrays\n        def _deep_transform_keys_in_object(object, &block)\n          case object\n          when ::Hash\n            object.each_with_object({}) do |(key, value), result|\n              result[yield(key)] = _deep_transform_keys_in_object(value, &block)\n            end\n          when Array\n            object.map { |e| _deep_transform_keys_in_object(e, &block) }\n          else\n            object\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/ext/string.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  module Ext\n    # Extend Hash through refinements\n    # taken from https://github.com/seamusabshere/to_regexp\n    module String\n      INLINE_OPTIONS = /[imxnesu]*/\n      REGEXP_DELIMITERS = {\n        '%r{' => '}',\n        '/' => '/'\n      }.freeze\n\n      refine ::String do\n        def literal?\n          REGEXP_DELIMITERS.none? { |s, e| start_with?(s) && self =~ /#{e}#{INLINE_OPTIONS}\\z/ }\n        end\n\n        def to_regexp(options = {})\n          if args = as_regexp(options)\n            ::Regexp.new(*args)\n          end\n        end\n\n        def as_regexp(options = {})\n          raise ::ArgumentError, \"[to_regexp] Options must be a Hash\" unless options.is_a?(::Hash)\n          str = self\n\n          return if options[:detect] && (str == '')\n\n          if options[:literal] || (options[:detect] && str.literal?)\n            content = ::Regexp.escape str\n          elsif delim_set = REGEXP_DELIMITERS.detect { |k, _| str.start_with?(k) }\n            delim_start, delim_end = delim_set\n            /\\A#{delim_start}(.*)#{delim_end}(#{INLINE_OPTIONS})\\z/u =~ str\n            content = Regexp.last_match(1)\n            inline_options = Regexp.last_match(2)\n            return unless content.is_a?(::String)\n            content.gsub! '\\\\/', '/'\n            if inline_options\n              options[:ignore_case] = true if inline_options.include?('i')\n              options[:multiline] = true if inline_options.include?('m')\n              options[:extended] = true if inline_options.include?('x')\n              # 'n', 'N' = none, 'e', 'E' = EUC, 's', 'S' = SJIS, 'u', 'U' = UTF-8\n              options[:lang] = inline_options.scan(/[nesu]/i).join.downcase\n            end\n          else\n            return\n          end\n\n          ignore_case = options[:ignore_case] ? ::Regexp::IGNORECASE : 0\n          multiline = options[:multiline] ? ::Regexp::MULTILINE : 0\n          extended = options[:extended] ? ::Regexp::EXTENDED : 0\n          lang = options[:lang] || ''\n          lang = lang.delete 'u' if (::RUBY_VERSION > '1.9') && lang.include?('u')\n\n          if lang.empty?\n            [content, (ignore_case | multiline | extended)]\n          else\n            [content, (ignore_case | multiline | extended), lang]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/rails.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  module Rails # :nodoc:\n    class << self\n      def connect!\n        require File.expand_path(\"config/environment\", Localer.config.app_path)\n        true\n      rescue LoadError\n        false\n      end\n\n      def translations\n        return {} unless connect!\n\n        I18n.backend.send(:init_translations)\n        I18n.backend.send(:translations)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/rake_task.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'rake'\nrequire 'rake/tasklib'\nrequire 'localer'\n\nmodule Localer\n  # Defines a Rake task for running Localer.\n  # The simplest use of it goes something like:\n  #\n  #   Localer::Rakeask.new\n  # This will define a task named <tt>localer</tt> described as 'Run Localer'.\n  class RakeTask < Rake::TaskLib\n    def initialize(name = :localer, *args) # rubocop:disable Lint/MissingSuper\n      @name = name\n      desc 'Run Localer'\n      task(name, *args) do |_, _task_args|\n        sh('localer check') do |ok, res|\n          exit res.exitstatus unless ok\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/localer/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule Localer\n  VERSION = \"0.2.0\"\nend\n"
  },
  {
    "path": "lib/localer.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"dry-initializer\"\nrequire_relative \"localer/version\"\nrequire_relative \"localer/rails\"\nrequire_relative \"localer/config\"\nrequire_relative \"localer/data\"\n\nmodule Localer # :nodoc:\n  using Localer::Ext::Hash\n  # using Localer::Ext::String\n\n  class << self\n    def data\n      @data ||= load_data\n    end\n\n    def config\n      @config ||= configure\n    end\n\n    def configure(options = {})\n      @config = Config.load(options)\n    end\n\n    def load_data(source = Localer::Rails.translations)\n      @data = Data.new(source)\n    end\n  end\nend\n"
  },
  {
    "path": "localer.gemspec",
    "content": "\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire \"localer/version\"\n\nGem::Specification.new do |spec|\n  spec.name          = \"localer\"\n  spec.version       = Localer::VERSION\n  spec.authors       = [\"Andrey Deryabin\"]\n  spec.email         = [\"deriabin@gmail.com\"]\n\n  spec.summary       = %q{Automatic detecting missing I18n translations tool.}\n  spec.description   = %q{Automatic detecting missing I18n translations tool.}\n  spec.homepage      = \"https://github.com/aderyabin/localer\"\n  spec.license       = \"MIT\"\n  spec.files         = `git ls-files -z`.split(\"\\x0\").reject do |f|\n    f.match(%r{^(test|spec|features|gemfiles)/})\n  end\n  spec.bindir        = \"bin\"\n  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }\n  spec.require_paths = [\"lib\"]\n\n  spec.add_development_dependency \"appraisal\"\n  spec.add_development_dependency \"bundler\", \"~> 1.17\"\n  spec.add_development_dependency \"rake\", \">= 12.3.3\"\n  spec.add_development_dependency \"rspec\", \"~> 3.0\"\n  spec.add_development_dependency \"rubocop\", \"~> 0.50\"\n  spec.add_development_dependency \"cucumber\"\n  spec.add_development_dependency \"aruba\"\n\n  spec.add_dependency \"thor\", \">= 0.19\"\n  spec.add_dependency \"dry-initializer\", \">= 2.0\"\nend\n"
  },
  {
    "path": "spec/dummy_app/config/application.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails\"\n\nclass DummyApp < Rails::Application\n  config.eager_load = false\nend\n"
  },
  {
    "path": "spec/dummy_app/config/environment.rb",
    "content": "# frozen_string_literal: true\n\n# Load the Rails application.\nrequire File.expand_path('application', __dir__)\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "spec/dummy_app/config/locales/en.rails.rb",
    "content": "# frozen_string_literal: true\n\n{\n  en: {\n    number: {\n      nth: {\n        ordinals: lambda do |_key, options|\n          number = options[:number]\n          case number\n          when 1 then \"st\"\n          when 2 then \"nd\"\n          when 3 then \"rd\"\n          when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 then \"th\"\n          else\n            num_modulo = number.to_i.abs % 100\n            num_modulo %= 10 if num_modulo > 13\n            case num_modulo\n            when 1 then \"st\"\n            when 2 then \"nd\"\n            when 3 then \"rd\"\n            else \"th\"\n            end\n          end\n        end,\n\n        ordinalized: lambda do |_key, options|\n          number = options[:number]\n          \"#{number}#{ActiveSupport::Inflector.ordinal(number)}\"\n        end\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spec/dummy_app/config/locales/en.rails.yml",
    "content": "en:\n  # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()\n  datetime:\n    distance_in_words:\n      half_a_minute: \"half a minute\"\n      less_than_x_seconds:\n        one:   \"less than 1 second\"\n        other: \"less than %{count} seconds\"\n      x_seconds:\n        one:   \"1 second\"\n        other: \"%{count} seconds\"\n      less_than_x_minutes:\n        one:   \"less than a minute\"\n        other: \"less than %{count} minutes\"\n      x_minutes:\n        one:   \"1 minute\"\n        other: \"%{count} minutes\"\n      about_x_hours:\n        one:   \"about 1 hour\"\n        other: \"about %{count} hours\"\n      x_days:\n        one:   \"1 day\"\n        other: \"%{count} days\"\n      about_x_months:\n        one:   \"about 1 month\"\n        other: \"about %{count} months\"\n      x_months:\n        one:   \"1 month\"\n        other: \"%{count} months\"\n      about_x_years:\n        one:   \"about 1 year\"\n        other: \"about %{count} years\"\n      over_x_years:\n        one:   \"over 1 year\"\n        other: \"over %{count} years\"\n      almost_x_years:\n        one:   \"almost 1 year\"\n        other: \"almost %{count} years\"\n    prompts:\n      year:   \"Year\"\n      month:  \"Month\"\n      day:    \"Day\"\n      hour:   \"Hour\"\n      minute: \"Minute\"\n      second: \"Seconds\"\n\n  helpers:\n    select:\n      # Default value for :prompt => true in FormOptionsHelper\n      prompt: \"Please select\"\n\n    # Default translation keys for submit and button FormHelper\n    submit:\n      create: 'Create %{model}'\n      update: 'Update %{model}'\n      submit: 'Save %{model}'\n  date:\n    formats:\n      # Use the strftime parameters for formats.\n      # When no format has been given, it uses default.\n      # You can provide other formats here if you like!\n      default: \"%Y-%m-%d\"\n      short: \"%b %d\"\n      long: \"%B %d, %Y\"\n\n    day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]\n    abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]\n\n    # Don't forget the nil at the beginning; there's no such thing as a 0th month\n    month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]\n    abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]\n    # Used in date_select and datetime_select.\n    order:\n      - year\n      - month\n      - day\n\n  time:\n    formats:\n      default: \"%a, %d %b %Y %H:%M:%S %z\"\n      short: \"%d %b %H:%M\"\n      long: \"%B %d, %Y %H:%M\"\n    am: \"am\"\n    pm: \"pm\"\n\n# Used in array.to_sentence.\n  support:\n    array:\n      words_connector: \", \"\n      two_words_connector: \" and \"\n      last_word_connector: \", and \"\n  number:\n    # Used in NumberHelper.number_to_delimited()\n    # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'\n    format:\n      # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)\n      separator: \".\"\n      # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)\n      delimiter: \",\"\n      # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)\n      precision: 3\n      # Determine how rounding is performed (see BigDecimal::mode)\n      round_mode: default\n      # If set to true, precision will mean the number of significant digits instead\n      # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)\n      significant: false\n      # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)\n      strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_currency()\n    currency:\n      format:\n        # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)\n        format: \"%u%n\"\n        unit: \"$\"\n        # These five are to override number.format and are optional\n        separator: \".\"\n        delimiter: \",\"\n        precision: 2\n        significant: false\n        strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_percentage()\n    percentage:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        # precision:\n        # significant: false\n        # strip_insignificant_zeros: false\n        format: \"%n%\"\n\n    # Used in NumberHelper.number_to_rounded()\n    precision:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        # precision:\n        # significant: false\n        # strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()\n    human:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        precision: 3\n        significant: true\n        strip_insignificant_zeros: true\n      # Used in number_to_human_size()\n      storage_units:\n        # Storage units output formatting.\n        # %u is the storage unit, %n is the number (default: 2 MB)\n        format: \"%n %u\"\n        units:\n          byte:\n            one:   \"Byte\"\n            other: \"Bytes\"\n          kb: \"KB\"\n          mb: \"MB\"\n          gb: \"GB\"\n          tb: \"TB\"\n          pb: \"PB\"\n          eb: \"EB\"\n      # Used in NumberHelper.number_to_human()\n      decimal_units:\n        format: \"%n %u\"\n        # Decimal units output formatting\n        # By default we will only quantify some of the exponents\n        # but the commented ones might be defined or overridden\n        # by the user.\n        units:\n          # femto: Quadrillionth\n          # pico: Trillionth\n          # nano: Billionth\n          # micro: Millionth\n          # mili: Thousandth\n          # centi: Hundredth\n          # deci: Tenth\n          unit: \"\"\n          # ten:\n          #   one: Ten\n          #   other: Tens\n          # hundred: Hundred\n          thousand: Thousand\n          million: Million\n          billion: Billion\n          trillion: Trillion\n          quadrillion: Quadrillion\n"
  },
  {
    "path": "spec/dummy_app/config/locales/ru.rails.rb",
    "content": "# frozen_string_literal: true\n\n{\n  ru: {\n    number: {\n      nth: {\n        ordinals: ->(_key, _options) {},\n        ordinalized: ->(_key, options) {}\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spec/dummy_app/config/locales/ru.rails.yml",
    "content": "ru:\n  date:\n    abbr_day_names:\n      -\n    abbr_month_names:\n      -\n    day_names:\n      -\n    month_names:\n      -\n    order:\n      -\n    formats:\n      default: \"%d.%m.%Y\"\n      long: \"%-d %B %Y\"\n      short: \"%-d %b\"\n  time:\n    am: утра\n    formats:\n      default: \"%a, %d %b %Y, %H:%M:%S %z\"\n      long: \"%d %B %Y, %H:%M\"\n      short: \"%d %b, %H:%M\"\n    pm: вечера\n  datetime:\n    prompts:\n      day: День\n      hour: Часов\n      minute: Минут\n      month: Месяц\n      second: Секунд\n      year: Год\n    distance_in_words:\n      about_x_hours:\n        one: около %{count} часа\n        other: около %{count} часа\n      about_x_months:\n        one: около %{count} месяца\n        other: около %{count} месяца\n      about_x_years:\n        one: около %{count} года\n        other: около %{count} лет\n      almost_x_years:\n        one: почти %{count} год\n        other: почти %{count} лет\n      half_a_minute: меньше минуты\n      less_than_x_minutes:\n        one: меньше %{count} минуты\n        other: меньше %{count} минуты\n      less_than_x_seconds:\n        one: меньше %{count} секунды\n        other: меньше %{count} секунды\n      over_x_years:\n        one: больше %{count} года\n        other: больше %{count} лет\n      x_days:\n        one: \"%{count} день\"\n        other: \"%{count} дня\"\n      x_minutes:\n        one: \"%{count} минуту\"\n        other: \"%{count} минуты\"\n      x_months:\n        one: \"%{count} месяц\"\n        other: \"%{count} месяца\"\n      x_seconds:\n        one: \"%{count} секунду\"\n        other: \"%{count} секунды\"\n  number:\n    precision:\n      format:\n        delimiter: ''\n    format:\n      delimiter: \" \"\n      precision: 3\n      separator: \",\"\n      round_mode: default\n      significant: false\n      strip_insignificant_zeros: false\n    percentage:\n      format:\n        delimiter: ''\n        format: \"%n%\"\n    human:\n      format:\n        delimiter: ''\n        precision: 1\n        significant: false\n        strip_insignificant_zeros: false\n      decimal_units:\n        format: \"%n %u\"\n        units:\n          unit: ''\n          billion: миллиард\n          million: миллион\n          quadrillion: квадриллион\n          thousand: тысяча\n          trillion: триллион\n      storage_units:\n        format: \"%n %u\"\n        units:\n          byte:\n            one: байт\n            other: байта\n          gb: ГБ\n          kb: КБ\n          mb: МБ\n          tb: ТБ\n          pb: \"\"\n          eb: \"\"\n\n    currency:\n      format:\n        delimiter: \" \"\n        format: \"%n %u\"\n        precision: 2\n        separator: \",\"\n        significant: false\n        strip_insignificant_zeros: false\n        unit: руб.\n  helpers:\n    select:\n      prompt: 'Выберите: '\n    submit:\n      create: Создать %{model}\n      submit: Сохранить %{model}\n      update: Сохранить %{model}\n  support:\n    array:\n      last_word_connector: \" и \"\n      two_words_connector: \" и \"\n      words_connector: \", \"\n"
  },
  {
    "path": "spec/dummy_app/config/locales/us.rails.rb",
    "content": "# frozen_string_literal: true\n\n{\n  us: {\n    number: {\n      nth: {\n        ordinals: lambda do |_key, options|\n          number = options[:number]\n          case number\n          when 1 then \"st\"\n          when 2 then \"nd\"\n          when 3 then \"rd\"\n          when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 then \"th\"\n          else\n            num_modulo = number.to_i.abs % 100\n            num_modulo %= 10 if num_modulo > 13\n            case num_modulo\n            when 1 then \"st\"\n            when 2 then \"nd\"\n            when 3 then \"rd\"\n            else \"th\"\n            end\n          end\n        end,\n\n        ordinalized: lambda do |_key, options|\n          number = options[:number]\n          \"#{number}#{ActiveSupport::Inflector.ordinal(number)}\"\n        end\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spec/dummy_app/config/locales/us.rails.yml",
    "content": "us:\n  # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()\n  datetime:\n    distance_in_words:\n      half_a_minute: \"half a minute\"\n      less_than_x_seconds:\n        one:   \"less than 1 second\"\n        other: \"less than %{count} seconds\"\n      x_seconds:\n        one:   \"1 second\"\n        other: \"%{count} seconds\"\n      less_than_x_minutes:\n        one:   \"less than a minute\"\n        other: \"less than %{count} minutes\"\n      x_minutes:\n        one:   \"1 minute\"\n        other: \"%{count} minutes\"\n      about_x_hours:\n        one:   \"about 1 hour\"\n        other: \"about %{count} hours\"\n      x_days:\n        one:   \"1 day\"\n        other: \"%{count} days\"\n      about_x_months:\n        one:   \"about 1 month\"\n        other: \"about %{count} months\"\n      x_months:\n        one:   \"1 month\"\n        other: \"%{count} months\"\n      about_x_years:\n        one:   \"about 1 year\"\n        other: \"about %{count} years\"\n      over_x_years:\n        one:   \"over 1 year\"\n        other: \"over %{count} years\"\n      almost_x_years:\n        one:   \"almost 1 year\"\n        other: \"almost %{count} years\"\n    prompts:\n      year:   \"Year\"\n      month:  \"Month\"\n      day:    \"Day\"\n      hour:   \"Hour\"\n      minute: \"Minute\"\n      second: \"Seconds\"\n\n  helpers:\n    select:\n      # Default value for :prompt => true in FormOptionsHelper\n      prompt: \"Please select\"\n\n    # Default translation keys for submit and button FormHelper\n    submit:\n      create: 'Create %{model}'\n      update: 'Update %{model}'\n      submit: 'Save %{model}'\n  date:\n    formats:\n      # Use the strftime parameters for formats.\n      # When no format has been given, it uses default.\n      # You can provide other formats here if you like!\n      default: \"%Y-%m-%d\"\n      short: \"%b %d\"\n      long: \"%B %d, %Y\"\n\n    day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]\n    abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]\n\n    # Don't forget the nil at the beginning; there's no such thing as a 0th month\n    month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]\n    abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]\n    # Used in date_select and datetime_select.\n    order:\n      - year\n      - month\n      - day\n\n  time:\n    formats:\n      default: \"%a, %d %b %Y %H:%M:%S %z\"\n      short: \"%d %b %H:%M\"\n      long: \"%B %d, %Y %H:%M\"\n    am: \"am\"\n    pm: \"pm\"\n\n# Used in array.to_sentence.\n  support:\n    array:\n      words_connector: \", \"\n      two_words_connector: \" and \"\n      last_word_connector: \", and \"\n  number:\n    # Used in NumberHelper.number_to_delimited()\n    # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'\n    format:\n      # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)\n      separator: \".\"\n      # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)\n      delimiter: \",\"\n      # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)\n      precision: 3\n      round_mode: default\n      # If set to true, precision will mean the number of significant digits instead\n      # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)\n      significant: false\n      # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)\n      strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_currency()\n    currency:\n      format:\n        # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)\n        format: \"%u%n\"\n        unit: \"$\"\n        # These five are to override number.format and are optional\n        separator: \".\"\n        delimiter: \",\"\n        precision: 2\n        significant: false\n        strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_percentage()\n    percentage:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        # precision:\n        # significant: false\n        # strip_insignificant_zeros: false\n        format: \"%n%\"\n\n    # Used in NumberHelper.number_to_rounded()\n    precision:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        # precision:\n        # significant: false\n        # strip_insignificant_zeros: false\n\n    # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()\n    human:\n      format:\n        # These five are to override number.format and are optional\n        # separator:\n        delimiter: \"\"\n        precision: 3\n        significant: true\n        strip_insignificant_zeros: true\n      # Used in number_to_human_size()\n      storage_units:\n        # Storage units output formatting.\n        # %u is the storage unit, %n is the number (default: 2 MB)\n        format: \"%n %u\"\n        units:\n          byte:\n            one:   \"Byte\"\n            other: \"Bytes\"\n          kb: \"KB\"\n          mb: \"MB\"\n          gb: \"GB\"\n          tb: \"TB\"\n          pb: \"PB\"\n          eb: \"EB\"\n      # Used in NumberHelper.number_to_human()\n      decimal_units:\n        format: \"%n %u\"\n        # Decimal units output formatting\n        # By default we will only quantify some of the exponents\n        # but the commented ones might be defined or overridden\n        # by the user.\n        units:\n          # femto: Quadrillionth\n          # pico: Trillionth\n          # nano: Billionth\n          # micro: Millionth\n          # mili: Thousandth\n          # centi: Hundredth\n          # deci: Tenth\n          unit: \"\"\n          # ten:\n          #   one: Ten\n          #   other: Tens\n          # hundred: Hundred\n          thousand: Thousand\n          million: Million\n          billion: Billion\n          trillion: Trillion\n          quadrillion: Quadrillion\n"
  },
  {
    "path": "spec/dummy_app/config.ru",
    "content": "# frozen_string_literal: true\n\nrequire ::File.expand_path(\"../config/environment\", __FILE__)\n\nRails.application.eager_load!\n\nrun Rails.application\n"
  },
  {
    "path": "spec/localer_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Localer do\n  it \"has a version number\" do\n    expect(Localer::VERSION).not_to be nil\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"bundler/setup\"\nrequire \"localer\"\n\nRSpec.configure do |config|\n  # Disable RSpec exposing methods globally on `Module` and `main`\n  config.disable_monkey_patching!\n\n  config.expect_with :rspec do |c|\n    c.syntax = :expect\n  end\nend\n"
  }
]