├── .github └── workflows │ ├── bundle-audit.yml │ ├── rubocop.yml │ └── test.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Appraisals ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── _config.yml ├── bin └── localer ├── features ├── global_exclude.feature ├── locale_option.feature ├── missing_app.feature ├── real_locales.feature ├── simple.feature ├── step_definitions │ └── additional_cli_steps.rb └── support │ └── env.rb ├── gemfiles ├── rails50.gemfile ├── rails50.gemfile.lock ├── rails51.gemfile ├── rails51.gemfile.lock ├── rails52.gemfile ├── rails52.gemfile.lock ├── rails60.gemfile ├── rails60.gemfile.lock ├── rails61.gemfile └── rails61.gemfile.lock ├── lib ├── localer.rb └── localer │ ├── config.rb │ ├── config │ └── locale.rb │ ├── data.rb │ ├── data │ ├── checker.rb │ ├── missing_translations.rb │ ├── processor.rb │ └── service.rb │ ├── ext │ ├── hash.rb │ └── string.rb │ ├── rails.rb │ ├── rake_task.rb │ └── version.rb ├── localer.gemspec ├── public ├── localer-full.png ├── localer-full2.png └── localer-logo.png └── spec ├── dummy_app ├── config.ru └── config │ ├── application.rb │ ├── environment.rb │ └── locales │ ├── en.rails.rb │ ├── en.rails.yml │ ├── ru.rails.rb │ ├── ru.rails.yml │ ├── us.rails.rb │ └── us.rails.yml ├── localer_spec.rb └── spec_helper.rb /.github/workflows/bundle-audit.yml: -------------------------------------------------------------------------------- 1 | name: Bundle Audit 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | rubocop: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 2.7 19 | - name: Patch-level verification for Bundler 20 | run: | 21 | gem install bundle-audit 22 | bundle-audit check --update 23 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: Lint Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | rubocop: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 2.7 17 | - name: Lint Ruby code with RuboCop 18 | run: | 19 | gem install rubocop 20 | rubocop 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | env: 13 | BUNDLE_JOBS: 4 14 | BUNDLE_RETRY: 3 15 | VERIFY_RESERVED: 1 16 | CI: true 17 | CUCUMBER_PUBLISH_QUIET: true 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | ruby: [2.7, 2.6, 2.5, 2.4] 22 | gemfile: [ 23 | 'gemfiles/rails50.gemfile', 24 | 'gemfiles/rails51.gemfile', 25 | 'gemfiles/rails52.gemfile', 26 | 'gemfiles/rails60.gemfile', 27 | 'gemfiles/rails61.gemfile' 28 | ] 29 | exclude: 30 | - ruby: 2.4 31 | gemfile: gemfiles/rails52.gemfile 32 | - ruby: 2.4 33 | gemfile: gemfiles/rails60.gemfile 34 | - ruby: 2.4 35 | gemfile: gemfiles/rails61.gemfile 36 | 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions/cache@v1 41 | with: 42 | path: /home/runner/bundle 43 | key: bundle-${{ matrix.ruby }}-${{ matrix.gemfile }}-${{ hashFiles(matrix.gemfile) }}-${{ hashFiles('**/*.gemspec') }} 44 | restore-keys: | 45 | bundle-${{ matrix.ruby }}-${{ matrix.gemfile }}- 46 | - uses: ruby/setup-ruby@v1 47 | with: 48 | ruby-version: ${{ matrix.ruby }} 49 | - name: Bundle install 50 | run: | 51 | bundle config path /home/runner/bundle 52 | bundle config --global gemfile ${{ matrix.gemfile }} 53 | bundle install 54 | bundle update 55 | - name: Run tests 56 | run: bundle exec rake 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | Gemfile.lock 11 | erl_crash.dump 12 | .ruby-version 13 | .vscode 14 | spec/dummy_app/log/ 15 | vendor/ 16 | .devcontainer/ 17 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | # Include gemspec and Rakefile 3 | Include: 4 | - "lib/**/*.rb" 5 | - "lib/**/*.rake" 6 | - "spec/**/*.rb" 7 | Exclude: 8 | - "bin/**/*" 9 | - "lib/localer/ext/*.rb" 10 | - "Appraisals" 11 | - "Gemfile" 12 | - "Rakefile" 13 | - "*.gemspec" 14 | - "spec/dummy_app/" 15 | DisplayCopNames: true 16 | NewCops: enable 17 | StyleGuideCopsOnly: false 18 | 19 | Naming/AccessorMethodName: 20 | Enabled: false 21 | 22 | Style/PercentLiteralDelimiters: 23 | Enabled: false 24 | 25 | Style/TrivialAccessors: 26 | Enabled: false 27 | 28 | Style/Documentation: 29 | Exclude: 30 | - "spec/**/*.rb" 31 | 32 | Style/StringLiterals: 33 | Enabled: false 34 | 35 | Style/BlockDelimiters: 36 | Exclude: 37 | - "spec/**/*.rb" 38 | 39 | Style/DoubleNegation: 40 | Enabled: false 41 | 42 | Style/HashEachMethods: 43 | Enabled: true 44 | 45 | Style/HashTransformKeys: 46 | Enabled: true 47 | 48 | Style/HashTransformValues: 49 | Enabled: true 50 | 51 | Layout/SpaceInsideStringInterpolation: 52 | EnforcedStyle: no_space 53 | 54 | Lint/AmbiguousRegexpLiteral: 55 | Enabled: false 56 | 57 | Lint/AmbiguousBlockAssociation: 58 | Enabled: false 59 | 60 | Metrics/MethodLength: 61 | Exclude: 62 | - "spec/**/*.rb" 63 | 64 | Layout/LineLength: 65 | Max: 120 66 | Exclude: 67 | - "spec/**/*.rb" 68 | 69 | Metrics/BlockLength: 70 | Exclude: 71 | - "spec/**/*.rb" 72 | 73 | Security/YAMLLoad: 74 | Enabled: false 75 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'rails50' do 2 | gem 'rails', '~> 5.0' 3 | end 4 | 5 | appraise 'rails51' do 6 | gem 'rails', '~> 5.1' 7 | end 8 | 9 | appraise 'rails52' do 10 | gem 'rails', '~> 5.2' 11 | end 12 | 13 | appraise 'rails60' do 14 | gem 'rails', '~> 6.0' 15 | end 16 | 17 | appraise 'rails61' do 18 | gem 'rails', '~> 6.1' 19 | end 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at deriabin@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in localer.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activesupport (6.1.4) 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | i18n (>= 1.6, < 2) 14 | minitest (>= 5.1) 15 | tzinfo (~> 2.0) 16 | zeitwerk (~> 2.3) 17 | appraisal (2.4.0) 18 | bundler 19 | rake 20 | thor (>= 0.14.0) 21 | aruba (1.1.2) 22 | bundler (>= 1.17, < 3.0) 23 | childprocess (>= 2.0, < 5.0) 24 | contracts (>= 0.16.0, < 0.18.0) 25 | cucumber (>= 2.4, < 7.0) 26 | rspec-expectations (~> 3.4) 27 | thor (~> 1.0) 28 | ast (2.4.2) 29 | builder (3.2.4) 30 | childprocess (4.1.0) 31 | concurrent-ruby (1.1.9) 32 | contracts (0.16.1) 33 | cucumber (6.1.0) 34 | builder (~> 3.2, >= 3.2.4) 35 | cucumber-core (~> 9.0, >= 9.0.1) 36 | cucumber-create-meta (~> 4.0, >= 4.0.0) 37 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 38 | cucumber-gherkin (~> 18.1, >= 18.1.0) 39 | cucumber-html-formatter (~> 13.0, >= 13.0.0) 40 | cucumber-messages (~> 15.0, >= 15.0.0) 41 | cucumber-wire (~> 5.0, >= 5.0.1) 42 | diff-lcs (~> 1.4, >= 1.4.4) 43 | mime-types (~> 3.3, >= 3.3.1) 44 | multi_test (~> 0.1, >= 0.1.2) 45 | sys-uname (~> 1.2, >= 1.2.2) 46 | cucumber-core (9.0.1) 47 | cucumber-gherkin (~> 18.1, >= 18.1.0) 48 | cucumber-messages (~> 15.0, >= 15.0.0) 49 | cucumber-tag-expressions (~> 3.0, >= 3.0.1) 50 | cucumber-create-meta (4.0.0) 51 | cucumber-messages (~> 15.0, >= 15.0.0) 52 | sys-uname (~> 1.2, >= 1.2.2) 53 | cucumber-cucumber-expressions (12.1.1) 54 | cucumber-gherkin (18.1.1) 55 | cucumber-messages (~> 15.0, >= 15.0.0) 56 | cucumber-html-formatter (13.0.0) 57 | cucumber-messages (~> 15.0, >= 15.0.0) 58 | cucumber-messages (15.0.0) 59 | protobuf-cucumber (~> 3.10, >= 3.10.8) 60 | cucumber-tag-expressions (3.0.1) 61 | cucumber-wire (5.0.1) 62 | cucumber-core (~> 9.0, >= 9.0.1) 63 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 64 | cucumber-messages (~> 15.0, >= 15.0.0) 65 | diff-lcs (1.4.4) 66 | dry-initializer (3.0.4) 67 | ffi (1.15.3) 68 | i18n (1.8.10) 69 | concurrent-ruby (~> 1.0) 70 | middleware (0.1.0) 71 | mime-types (3.3.1) 72 | mime-types-data (~> 3.2015) 73 | mime-types-data (3.2021.0225) 74 | minitest (5.14.4) 75 | multi_test (0.1.2) 76 | parallel (1.20.1) 77 | parser (3.0.1.1) 78 | ast (~> 2.4.1) 79 | protobuf-cucumber (3.10.8) 80 | activesupport (>= 3.2) 81 | middleware 82 | thor 83 | thread_safe 84 | rainbow (3.0.0) 85 | rake (13.0.3) 86 | regexp_parser (2.1.1) 87 | rexml (3.2.5) 88 | rspec (3.10.0) 89 | rspec-core (~> 3.10.0) 90 | rspec-expectations (~> 3.10.0) 91 | rspec-mocks (~> 3.10.0) 92 | rspec-core (3.10.1) 93 | rspec-support (~> 3.10.0) 94 | rspec-expectations (3.10.1) 95 | diff-lcs (>= 1.2.0, < 2.0) 96 | rspec-support (~> 3.10.0) 97 | rspec-mocks (3.10.2) 98 | diff-lcs (>= 1.2.0, < 2.0) 99 | rspec-support (~> 3.10.0) 100 | rspec-support (3.10.2) 101 | rubocop (0.93.1) 102 | parallel (~> 1.10) 103 | parser (>= 2.7.1.5) 104 | rainbow (>= 2.2.2, < 4.0) 105 | regexp_parser (>= 1.8) 106 | rexml 107 | rubocop-ast (>= 0.6.0) 108 | ruby-progressbar (~> 1.7) 109 | unicode-display_width (>= 1.4.0, < 2.0) 110 | rubocop-ast (1.7.0) 111 | parser (>= 3.0.1.1) 112 | ruby-progressbar (1.11.0) 113 | sys-uname (1.2.2) 114 | ffi (~> 1.1) 115 | thor (1.1.0) 116 | thread_safe (0.3.6) 117 | tzinfo (2.0.4) 118 | concurrent-ruby (~> 1.0) 119 | unicode-display_width (1.7.0) 120 | zeitwerk (2.4.2) 121 | 122 | PLATFORMS 123 | ruby 124 | 125 | DEPENDENCIES 126 | appraisal 127 | aruba 128 | bundler (~> 1.17) 129 | cucumber 130 | localer! 131 | rake (>= 12.3.3) 132 | rspec (~> 3.0) 133 | rubocop (~> 0.50) 134 | 135 | BUNDLED WITH 136 | 1.17.3 137 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Andrey Deryabin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | [![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) 7 | 8 | Localer is a tool that automatically detects missing I18n translations. 9 | 10 | The 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. 11 | 12 |

13 | 14 |

15 | 16 | ## Installation 17 | 18 | Add this line to your application's Gemfile: 19 | 20 | ```ruby 21 | gem 'localer' 22 | ``` 23 | 24 | And then execute: 25 | 26 | $ bundle 27 | 28 | Or install it yourself as: 29 | 30 | $ gem install localer 31 | 32 | ## Usage 33 | 34 | At the root directory of a Rails app, run: 35 | 36 | $ localer check . 37 | 38 | or for specific Rails path: 39 | 40 | $ localer check /path/to/rails/application 41 | 42 | ## CI integration 43 | 44 | Localer is easy to integrate into your favorite CI workflow: 45 | ```yml 46 | # .travis.yml 47 | 48 | # other configuration options 49 | script: 50 | - bundle exec bundle-audit 51 | - bundle exec rubocop 52 | - bundle exec rspec 53 | - bundle exec localer 54 | ``` 55 | 56 | or 57 | 58 | ```ruby 59 | # Rakefile 60 | 61 | # other requirements 62 | require 'localer/rake_task' 63 | Localer::RakeTask.new() 64 | 65 | task(:default).clear 66 | task default: [:rubocop, :spec, :localer] 67 | ``` 68 | 69 | ## Support 70 | 71 | Localer supports 72 | * Ruby: 2.4, 2.5, 2.6, 2.7 73 | * Rails: 5.0, 5.1, 5.2, 6.0, 6.1 74 | 75 | ## Configuration 76 | 77 | The 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. 78 | 79 | #### Disable specific locale 80 | 81 | By default, Localer enables all locales, but you can disable it: 82 | 83 | ```yml 84 | Locale: 85 | EN: 86 | Enabled: false 87 | ``` 88 | 89 | #### Exclude keys globally 90 | By default, Localer enables all keys, but you can disable keys started with specified string or by regex: 91 | 92 | ```yml 93 | Exclude: 94 | - /population\z/ 95 | - .countries.france 96 | ``` 97 | 98 | #### Exclude keys for specific locale 99 | ```yml 100 | Locale: 101 | EN: 102 | Exclude: 103 | - /population\z/ 104 | - .countries.france 105 | ``` 106 | 107 | ## Using Rake 108 | 109 | Localer 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`: 110 | 111 | ```ruby 112 | require 'rubygems' 113 | require 'localer' 114 | require 'localer/rake_task' 115 | 116 | Localer::RakeTask.new(:localer) 117 | ``` 118 | 119 | When you now run: 120 | 121 | $ rake -T 122 | 123 | you should see 124 | 125 | ``` 126 | rake localer # Run Localer 127 | ``` 128 | 129 | ## Development 130 | 131 | After 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. 132 | 133 | ## Built With 134 | 135 | * [Thor](https://github.com/erikhuda/thor) - Used for building command-line interfaces. 136 | * [Appraisal](https://github.com/thoughtbot/appraisal) - Used for testing against different versions of dependencies 137 | * [Cucumber](https://github.com/cucumber/cucumber) + [Aruba](https://github.com/cucumber/aruba) - Used for testing command-line commands 138 | 139 | ## Acknowledge 140 | Special thanks to [Roman Shamin](https://www.facebook.com/romanshamin) for the logo. 141 | 142 | ## Contributing 143 | 144 | Bug 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. 145 | 146 | ## License 147 | 148 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 149 | 150 | ## Code of Conduct 151 | 152 | Everyone 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). 153 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rubocop/rake_task" 3 | require 'cucumber/rake/task' 4 | 5 | RuboCop::RakeTask.new 6 | 7 | Cucumber::Rake::Task.new(:features) do |t| 8 | t.cucumber_opts = "features --format pretty" 9 | end 10 | 11 | task :default do 12 | if ENV["RUBOCOP"] 13 | Rake::Task["rubocop"].invoke 14 | else 15 | Rake::Task["features"].invoke 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-leap-day -------------------------------------------------------------------------------- /bin/localer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "thor" 4 | require "irb" 5 | require_relative "../lib/localer" 6 | 7 | module Localer 8 | class CLI < Thor 9 | desc "version", "Print Localer version" 10 | def version 11 | say Localer::VERSION 12 | end 13 | 14 | desc "check [/path/to/rails/application]", "Check missing translations" 15 | def check(app_path = Localer::Config::APP_PATH) 16 | Localer.configure(options.dup.merge(app_path: app_path)) 17 | 18 | connect_to_rails 19 | 20 | if Localer.data.complete? 21 | say "\xE2\x9C\x94 No missing translations found.", :green 22 | else 23 | missing_translations = Localer.data.missing_translations 24 | say "\xE2\x9C\x96 Missing translations found (#{missing_translations.count}):", :red 25 | missing_translations.each do |tr| 26 | say "* #{tr}" 27 | end 28 | 29 | exit 1 30 | end 31 | end 32 | 33 | default_task :check 34 | 35 | private 36 | 37 | def connect_to_rails 38 | return if Localer::Rails.connect! 39 | say "No Rails application found" 40 | exit 1 41 | end 42 | end 43 | end 44 | 45 | 46 | Localer::CLI.start(ARGV) 47 | -------------------------------------------------------------------------------- /features/global_exclude.feature: -------------------------------------------------------------------------------- 1 | Feature: Localer 2 | Scenario: Exclude strings 3 | Given a real locales 4 | Given a config file with: 5 | """ 6 | Exclude: 7 | - .countries.france 8 | - .population 9 | """ 10 | When I run checker 11 | Then the checker should pass 12 | 13 | Scenario: Exclude regexp 14 | Given a real locales 15 | Given a config file with: 16 | """ 17 | Exclude: 18 | - /population\z/ 19 | """ 20 | When I run checker 21 | Then the checker should pass 22 | 23 | Scenario: Exclude strict regexp 24 | Given a real locales 25 | Given a config file with: 26 | """ 27 | Exclude: 28 | - /^.population/ 29 | """ 30 | When I run checker 31 | Then the checker should returns 1 missing translations: 32 | | en.countries.france.population | 33 | -------------------------------------------------------------------------------- /features/locale_option.feature: -------------------------------------------------------------------------------- 1 | Feature: Localer 2 | Scenario: Disable en locale 3 | Given a real locales 4 | Given a config file with: 5 | """ 6 | Locale: 7 | en: 8 | Enabled: false 9 | """ 10 | When I run checker 11 | Then the checker should pass 12 | 13 | Scenario: Disable case-insensitive EN locale 14 | Given a real locales 15 | Given a config file with: 16 | """ 17 | Locale: 18 | EN: 19 | Enabled: false 20 | """ 21 | When I run checker 22 | Then the checker should pass 23 | 24 | Scenario: Disable en.population 25 | Given a real locales 26 | Given a config file with: 27 | """ 28 | Locale: 29 | en: 30 | Exclude: 31 | - .population.italy 32 | """ 33 | When I run checker 34 | Then the checker should returns 2 missing translations: 35 | | en.countries.france.population | 36 | | en.population.france | 37 | 38 | Scenario: With empty config file 39 | Given a real locales 40 | Given a config file with: 41 | """ 42 | """ 43 | When I run checker 44 | Then the checker should returns 4 missing translations: 45 | | ru.population.italy | 46 | | us.population.italy | 47 | | en.countries.france.population | 48 | | en.population.france | 49 | -------------------------------------------------------------------------------- /features/missing_app.feature: -------------------------------------------------------------------------------- 1 | Feature: Localer 2 | 3 | Scenario: No rails application 4 | When I run `localer check` 5 | Then the checker should not found rails application 6 | 7 | Scenario: No rails application at existed paths 8 | When I run `localer check` 9 | Then the checker should not found rails application 10 | 11 | Scenario: No rails application at not non-existed 12 | When I run `localer check non-existed_path` 13 | Then the checker should not found rails application 14 | -------------------------------------------------------------------------------- /features/real_locales.feature: -------------------------------------------------------------------------------- 1 | Feature: Localer 2 | 3 | Scenario: Real locales does not pass 4 | Given a real locales 5 | When I run checker 6 | Then the checker should returns 4 missing translations: 7 | | ru.population.italy | 8 | | us.population.italy | 9 | | en.population.france | 10 | | en.countries.france.population | -------------------------------------------------------------------------------- /features/simple.feature: -------------------------------------------------------------------------------- 1 | Feature: Localer 2 | 3 | Scenario: No locales files 4 | When I run checker 5 | Then the checker should pass 6 | 7 | Scenario: Empty en locale 8 | Given a "en" locale file with: 9 | """ 10 | en: 11 | """ 12 | When I run checker 13 | Then the checker should pass 14 | 15 | Scenario: Empty ru locale 16 | Given a "ru" locale file with: 17 | """ 18 | ru: 19 | """ 20 | When I run checker 21 | Then the checker should pass 22 | 23 | Scenario: Complete locales 24 | Given a "en" locale file with: 25 | """ 26 | en: 27 | one: one 28 | """ 29 | Given a "ru" locale file with: 30 | """ 31 | ru: 32 | one: один 33 | """ 34 | Given a "us" locale file with: 35 | """ 36 | us: 37 | one: one 38 | """ 39 | When I run checker 40 | Then the checker should pass 41 | 42 | Scenario: Empty en locale 43 | Given a "ru" locale file with: 44 | """ 45 | ru: 46 | one: один 47 | """ 48 | When I run checker 49 | Then the checker should fail 50 | 51 | Scenario: Incorrect structure 52 | Given a "en" locale file with: 53 | """ 54 | en: 55 | too_long: "Too Long" 56 | """ 57 | Given a "ru" locale file with: 58 | """ 59 | ru: 60 | too_long: 61 | one: слишком большой длины (не может быть больше чем %{count} символ) 62 | other: слишком большой длины (не может быть больше чем %{count} символа) 63 | """ 64 | When I run checker 65 | Then the checker should fail 66 | -------------------------------------------------------------------------------- /features/step_definitions/additional_cli_steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Given /^a "(.*)" locale file with:$/ do |locale, file_content| 4 | write_file("#{LOCALE_DIR}/#{locale}.yml", file_content) 5 | end 6 | 7 | Given /^a config file with:$/ do |file_content| 8 | write_file(CONFIG_PATH, file_content) 9 | end 10 | 11 | Given /^a real locales$/ do # rubocop:disable Metrics/BlockLength 12 | steps %{ 13 | Given a "en" locale file with: 14 | """ 15 | en: 16 | population: 17 | italy: 60.6 18 | countries: 19 | italy: 20 | city: Rome 21 | spain: 22 | city: Madrid 23 | france: 24 | city: Paris 25 | """ 26 | Given a "ru" locale file with: 27 | """ 28 | ru: 29 | population: 30 | france: 66.9 31 | countries: 32 | italy: 33 | city: Рим 34 | spain: 35 | city: Мадрид 36 | france: 37 | city: Париж 38 | population: 66.9 39 | """ 40 | Given a "us" locale file with: 41 | """ 42 | us: 43 | population: 44 | france: 66.9 45 | countries: 46 | italy: 47 | city: Rome 48 | spain: 49 | city: Madrid 50 | france: 51 | city: Paris 52 | population: 66.9 53 | """ 54 | } 55 | end 56 | 57 | Then /^the checker should pass$/ do 58 | step 'the output should contain "✔ No missing translations found"' 59 | step 'the exit status should be 0' 60 | end 61 | 62 | Then /^the checker should fail$/ do 63 | step 'the output should contain "✖ Missing translations found"' 64 | step 'the exit status should be 1' 65 | end 66 | 67 | Then /^the checker should returns (.*) missing translations:$/ do |int, translations| 68 | step %{the output should contain "✖ Missing translations found (#{int})"} 69 | translations.raw.each do |tr| 70 | step %{the output should match /^#{Regexp.escape('* ' + tr[0])}$/} 71 | end 72 | 73 | step 'the exit status should be 1' 74 | end 75 | 76 | Then /^the checker should not found rails application$/ do 77 | step 'the output should contain "No Rails application found"' 78 | step 'the exit status should be 1' 79 | end 80 | 81 | When /^I run checker$/ do 82 | if defined?(run) 83 | run("localer check ../../spec/dummy_app") 84 | else 85 | run_command("localer check ../../spec/dummy_app") 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aruba/cucumber' 4 | 5 | DUMMY_APP_DIR = "../../spec/dummy_app" 6 | LOCALE_DIR = "#{DUMMY_APP_DIR}/config/locales" 7 | CONFIG_PATH = "#{DUMMY_APP_DIR}/.localer.yml" 8 | 9 | After do |_| 10 | %w[ru en us].each do |locale| 11 | path = "#{LOCALE_DIR}/#{locale}.yml" 12 | remove(path) if exist?(path) 13 | end 14 | 15 | remove(CONFIG_PATH) if exist?(CONFIG_PATH) 16 | end 17 | -------------------------------------------------------------------------------- /gemfiles/rails50.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails50.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (5.0.0) 12 | actionpack (= 5.0.0) 13 | nio4r (~> 1.2) 14 | websocket-driver (~> 0.6.1) 15 | actionmailer (5.0.0) 16 | actionpack (= 5.0.0) 17 | actionview (= 5.0.0) 18 | activejob (= 5.0.0) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (5.0.0) 22 | actionview (= 5.0.0) 23 | activesupport (= 5.0.0) 24 | rack (~> 2.0) 25 | rack-test (~> 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 28 | actionview (5.0.0) 29 | activesupport (= 5.0.0) 30 | builder (~> 3.1) 31 | erubis (~> 2.7.0) 32 | rails-dom-testing (~> 2.0) 33 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 34 | activejob (5.0.0) 35 | activesupport (= 5.0.0) 36 | globalid (>= 0.3.6) 37 | activemodel (5.0.0) 38 | activesupport (= 5.0.0) 39 | activerecord (5.0.0) 40 | activemodel (= 5.0.0) 41 | activesupport (= 5.0.0) 42 | arel (~> 7.0) 43 | activesupport (5.0.0) 44 | concurrent-ruby (~> 1.0, >= 1.0.2) 45 | i18n (~> 0.7) 46 | minitest (~> 5.1) 47 | tzinfo (~> 1.1) 48 | appraisal (2.2.0) 49 | bundler 50 | rake 51 | thor (>= 0.14.0) 52 | arel (7.1.4) 53 | aruba (0.14.3) 54 | childprocess (~> 0.8.0) 55 | contracts (~> 0.9) 56 | cucumber (>= 1.3.19) 57 | ffi (~> 1.9.10) 58 | rspec-expectations (>= 2.99) 59 | thor (~> 0.19) 60 | ast (2.4.0) 61 | backports (3.11.1) 62 | builder (3.2.3) 63 | childprocess (0.8.0) 64 | ffi (~> 1.0, >= 1.0.11) 65 | concurrent-ruby (1.0.5) 66 | contracts (0.16.0) 67 | crass (1.0.3) 68 | cucumber (3.1.0) 69 | builder (>= 2.1.2) 70 | cucumber-core (~> 3.1.0) 71 | cucumber-expressions (~> 5.0.4) 72 | cucumber-wire (~> 0.0.1) 73 | diff-lcs (~> 1.3) 74 | gherkin (~> 5.0) 75 | multi_json (>= 1.7.5, < 2.0) 76 | multi_test (>= 0.1.2) 77 | cucumber-core (3.1.0) 78 | backports (>= 3.8.0) 79 | cucumber-tag_expressions (~> 1.1.0) 80 | gherkin (>= 5.0.0) 81 | cucumber-expressions (5.0.13) 82 | cucumber-tag_expressions (1.1.1) 83 | cucumber-wire (0.0.1) 84 | diff-lcs (1.3) 85 | dry-initializer (3.0.3) 86 | erubis (2.7.0) 87 | ffi (1.9.21) 88 | gherkin (5.0.0) 89 | globalid (0.4.1) 90 | activesupport (>= 4.2.0) 91 | i18n (0.9.3) 92 | concurrent-ruby (~> 1.0) 93 | loofah (2.1.1) 94 | crass (~> 1.0.2) 95 | nokogiri (>= 1.5.9) 96 | mail (2.7.0) 97 | mini_mime (>= 0.1.1) 98 | method_source (0.9.0) 99 | mini_mime (1.0.0) 100 | mini_portile2 (2.3.0) 101 | minitest (5.11.3) 102 | multi_json (1.13.1) 103 | multi_test (0.1.2) 104 | nio4r (1.2.1) 105 | nokogiri (1.8.2) 106 | mini_portile2 (~> 2.3.0) 107 | parallel (1.12.1) 108 | parser (2.4.0.2) 109 | ast (~> 2.3) 110 | powerpack (0.1.1) 111 | rack (2.0.3) 112 | rack-test (0.6.3) 113 | rack (>= 1.0) 114 | rails (5.0.0) 115 | actioncable (= 5.0.0) 116 | actionmailer (= 5.0.0) 117 | actionpack (= 5.0.0) 118 | actionview (= 5.0.0) 119 | activejob (= 5.0.0) 120 | activemodel (= 5.0.0) 121 | activerecord (= 5.0.0) 122 | activesupport (= 5.0.0) 123 | bundler (>= 1.3.0, < 2.0) 124 | railties (= 5.0.0) 125 | sprockets-rails (>= 2.0.0) 126 | rails-dom-testing (2.0.3) 127 | activesupport (>= 4.2.0) 128 | nokogiri (>= 1.6) 129 | rails-html-sanitizer (1.0.3) 130 | loofah (~> 2.0) 131 | railties (5.0.0) 132 | actionpack (= 5.0.0) 133 | activesupport (= 5.0.0) 134 | method_source 135 | rake (>= 0.8.7) 136 | thor (>= 0.18.1, < 2.0) 137 | rainbow (3.0.0) 138 | rake (13.0.1) 139 | rspec (3.7.0) 140 | rspec-core (~> 3.7.0) 141 | rspec-expectations (~> 3.7.0) 142 | rspec-mocks (~> 3.7.0) 143 | rspec-core (3.7.1) 144 | rspec-support (~> 3.7.0) 145 | rspec-expectations (3.7.0) 146 | diff-lcs (>= 1.2.0, < 2.0) 147 | rspec-support (~> 3.7.0) 148 | rspec-mocks (3.7.0) 149 | diff-lcs (>= 1.2.0, < 2.0) 150 | rspec-support (~> 3.7.0) 151 | rspec-support (3.7.1) 152 | rubocop (0.52.1) 153 | parallel (~> 1.10) 154 | parser (>= 2.4.0.2, < 3.0) 155 | powerpack (~> 0.1) 156 | rainbow (>= 2.2.2, < 4.0) 157 | ruby-progressbar (~> 1.7) 158 | unicode-display_width (~> 1.0, >= 1.0.1) 159 | ruby-progressbar (1.9.0) 160 | sprockets (3.7.1) 161 | concurrent-ruby (~> 1.0) 162 | rack (> 1, < 3) 163 | sprockets-rails (3.2.1) 164 | actionpack (>= 4.0) 165 | activesupport (>= 4.0) 166 | sprockets (>= 3.0.0) 167 | thor (0.19.0) 168 | thread_safe (0.3.6) 169 | tzinfo (1.2.4) 170 | thread_safe (~> 0.1) 171 | unicode-display_width (1.3.0) 172 | websocket-driver (0.6.5) 173 | websocket-extensions (>= 0.1.0) 174 | websocket-extensions (0.1.3) 175 | 176 | PLATFORMS 177 | ruby 178 | 179 | DEPENDENCIES 180 | appraisal 181 | aruba 182 | bundler (~> 1.17) 183 | cucumber 184 | localer! 185 | rails (~> 5.0) 186 | rake (>= 12.3.3) 187 | rspec (~> 3.0) 188 | rubocop (~> 0.50) 189 | 190 | BUNDLED WITH 191 | 1.17.3 192 | -------------------------------------------------------------------------------- /gemfiles/rails51.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails51.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (5.1.0) 12 | actionpack (= 5.1.0) 13 | nio4r (~> 2.0) 14 | websocket-driver (~> 0.6.1) 15 | actionmailer (5.1.0) 16 | actionpack (= 5.1.0) 17 | actionview (= 5.1.0) 18 | activejob (= 5.1.0) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (5.1.0) 22 | actionview (= 5.1.0) 23 | activesupport (= 5.1.0) 24 | rack (~> 2.0) 25 | rack-test (~> 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 28 | actionview (5.1.0) 29 | activesupport (= 5.1.0) 30 | builder (~> 3.1) 31 | erubi (~> 1.4) 32 | rails-dom-testing (~> 2.0) 33 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 34 | activejob (5.1.0) 35 | activesupport (= 5.1.0) 36 | globalid (>= 0.3.6) 37 | activemodel (5.1.0) 38 | activesupport (= 5.1.0) 39 | activerecord (5.1.0) 40 | activemodel (= 5.1.0) 41 | activesupport (= 5.1.0) 42 | arel (~> 8.0) 43 | activesupport (5.1.0) 44 | concurrent-ruby (~> 1.0, >= 1.0.2) 45 | i18n (~> 0.7) 46 | minitest (~> 5.1) 47 | tzinfo (~> 1.1) 48 | appraisal (2.2.0) 49 | bundler 50 | rake 51 | thor (>= 0.14.0) 52 | arel (8.0.0) 53 | aruba (0.14.3) 54 | childprocess (~> 0.8.0) 55 | contracts (~> 0.9) 56 | cucumber (>= 1.3.19) 57 | ffi (~> 1.9.10) 58 | rspec-expectations (>= 2.99) 59 | thor (~> 0.19) 60 | ast (2.4.0) 61 | backports (3.11.1) 62 | builder (3.2.3) 63 | childprocess (0.8.0) 64 | ffi (~> 1.0, >= 1.0.11) 65 | concurrent-ruby (1.0.5) 66 | contracts (0.16.0) 67 | crass (1.0.3) 68 | cucumber (3.1.0) 69 | builder (>= 2.1.2) 70 | cucumber-core (~> 3.1.0) 71 | cucumber-expressions (~> 5.0.4) 72 | cucumber-wire (~> 0.0.1) 73 | diff-lcs (~> 1.3) 74 | gherkin (~> 5.0) 75 | multi_json (>= 1.7.5, < 2.0) 76 | multi_test (>= 0.1.2) 77 | cucumber-core (3.1.0) 78 | backports (>= 3.8.0) 79 | cucumber-tag_expressions (~> 1.1.0) 80 | gherkin (>= 5.0.0) 81 | cucumber-expressions (5.0.13) 82 | cucumber-tag_expressions (1.1.1) 83 | cucumber-wire (0.0.1) 84 | diff-lcs (1.3) 85 | dry-initializer (3.0.3) 86 | erubi (1.7.0) 87 | ffi (1.9.21) 88 | gherkin (5.0.0) 89 | globalid (0.4.1) 90 | activesupport (>= 4.2.0) 91 | i18n (0.9.3) 92 | concurrent-ruby (~> 1.0) 93 | loofah (2.1.1) 94 | crass (~> 1.0.2) 95 | nokogiri (>= 1.5.9) 96 | mail (2.7.0) 97 | mini_mime (>= 0.1.1) 98 | method_source (0.9.0) 99 | mini_mime (1.0.0) 100 | mini_portile2 (2.3.0) 101 | minitest (5.11.3) 102 | multi_json (1.13.1) 103 | multi_test (0.1.2) 104 | nio4r (2.2.0) 105 | nokogiri (1.8.2) 106 | mini_portile2 (~> 2.3.0) 107 | parallel (1.12.1) 108 | parser (2.4.0.2) 109 | ast (~> 2.3) 110 | powerpack (0.1.1) 111 | rack (2.0.3) 112 | rack-test (0.6.3) 113 | rack (>= 1.0) 114 | rails (5.1.0) 115 | actioncable (= 5.1.0) 116 | actionmailer (= 5.1.0) 117 | actionpack (= 5.1.0) 118 | actionview (= 5.1.0) 119 | activejob (= 5.1.0) 120 | activemodel (= 5.1.0) 121 | activerecord (= 5.1.0) 122 | activesupport (= 5.1.0) 123 | bundler (>= 1.3.0, < 2.0) 124 | railties (= 5.1.0) 125 | sprockets-rails (>= 2.0.0) 126 | rails-dom-testing (2.0.3) 127 | activesupport (>= 4.2.0) 128 | nokogiri (>= 1.6) 129 | rails-html-sanitizer (1.0.3) 130 | loofah (~> 2.0) 131 | railties (5.1.0) 132 | actionpack (= 5.1.0) 133 | activesupport (= 5.1.0) 134 | method_source 135 | rake (>= 0.8.7) 136 | thor (>= 0.18.1, < 2.0) 137 | rainbow (3.0.0) 138 | rake (13.0.1) 139 | rspec (3.7.0) 140 | rspec-core (~> 3.7.0) 141 | rspec-expectations (~> 3.7.0) 142 | rspec-mocks (~> 3.7.0) 143 | rspec-core (3.7.1) 144 | rspec-support (~> 3.7.0) 145 | rspec-expectations (3.7.0) 146 | diff-lcs (>= 1.2.0, < 2.0) 147 | rspec-support (~> 3.7.0) 148 | rspec-mocks (3.7.0) 149 | diff-lcs (>= 1.2.0, < 2.0) 150 | rspec-support (~> 3.7.0) 151 | rspec-support (3.7.1) 152 | rubocop (0.52.1) 153 | parallel (~> 1.10) 154 | parser (>= 2.4.0.2, < 3.0) 155 | powerpack (~> 0.1) 156 | rainbow (>= 2.2.2, < 4.0) 157 | ruby-progressbar (~> 1.7) 158 | unicode-display_width (~> 1.0, >= 1.0.1) 159 | ruby-progressbar (1.9.0) 160 | sprockets (3.7.1) 161 | concurrent-ruby (~> 1.0) 162 | rack (> 1, < 3) 163 | sprockets-rails (3.2.1) 164 | actionpack (>= 4.0) 165 | activesupport (>= 4.0) 166 | sprockets (>= 3.0.0) 167 | thor (0.19.0) 168 | thread_safe (0.3.6) 169 | tzinfo (1.2.4) 170 | thread_safe (~> 0.1) 171 | unicode-display_width (1.3.0) 172 | websocket-driver (0.6.5) 173 | websocket-extensions (>= 0.1.0) 174 | websocket-extensions (0.1.3) 175 | 176 | PLATFORMS 177 | ruby 178 | 179 | DEPENDENCIES 180 | appraisal 181 | aruba 182 | bundler (~> 1.17) 183 | cucumber 184 | localer! 185 | rails (~> 5.1) 186 | rake (>= 12.3.3) 187 | rspec (~> 3.0) 188 | rubocop (~> 0.50) 189 | 190 | BUNDLED WITH 191 | 1.17.3 192 | -------------------------------------------------------------------------------- /gemfiles/rails52.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.2" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails52.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (5.2.6) 12 | actionpack (= 5.2.6) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailer (5.2.6) 16 | actionpack (= 5.2.6) 17 | actionview (= 5.2.6) 18 | activejob (= 5.2.6) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (5.2.6) 22 | actionview (= 5.2.6) 23 | activesupport (= 5.2.6) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 28 | actionview (5.2.6) 29 | activesupport (= 5.2.6) 30 | builder (~> 3.1) 31 | erubi (~> 1.4) 32 | rails-dom-testing (~> 2.0) 33 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 34 | activejob (5.2.6) 35 | activesupport (= 5.2.6) 36 | globalid (>= 0.3.6) 37 | activemodel (5.2.6) 38 | activesupport (= 5.2.6) 39 | activerecord (5.2.6) 40 | activemodel (= 5.2.6) 41 | activesupport (= 5.2.6) 42 | arel (>= 9.0) 43 | activestorage (5.2.6) 44 | actionpack (= 5.2.6) 45 | activerecord (= 5.2.6) 46 | marcel (~> 1.0.0) 47 | activesupport (5.2.6) 48 | concurrent-ruby (~> 1.0, >= 1.0.2) 49 | i18n (>= 0.7, < 2) 50 | minitest (~> 5.1) 51 | tzinfo (~> 1.1) 52 | appraisal (2.4.0) 53 | bundler 54 | rake 55 | thor (>= 0.14.0) 56 | arel (9.0.0) 57 | aruba (1.1.2) 58 | bundler (>= 1.17, < 3.0) 59 | childprocess (>= 2.0, < 5.0) 60 | contracts (>= 0.16.0, < 0.18.0) 61 | cucumber (>= 2.4, < 7.0) 62 | rspec-expectations (~> 3.4) 63 | thor (~> 1.0) 64 | ast (2.4.2) 65 | builder (3.2.4) 66 | childprocess (4.1.0) 67 | concurrent-ruby (1.1.9) 68 | contracts (0.16.1) 69 | crass (1.0.6) 70 | cucumber (6.1.0) 71 | builder (~> 3.2, >= 3.2.4) 72 | cucumber-core (~> 9.0, >= 9.0.1) 73 | cucumber-create-meta (~> 4.0, >= 4.0.0) 74 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 75 | cucumber-gherkin (~> 18.1, >= 18.1.0) 76 | cucumber-html-formatter (~> 13.0, >= 13.0.0) 77 | cucumber-messages (~> 15.0, >= 15.0.0) 78 | cucumber-wire (~> 5.0, >= 5.0.1) 79 | diff-lcs (~> 1.4, >= 1.4.4) 80 | mime-types (~> 3.3, >= 3.3.1) 81 | multi_test (~> 0.1, >= 0.1.2) 82 | sys-uname (~> 1.2, >= 1.2.2) 83 | cucumber-core (9.0.1) 84 | cucumber-gherkin (~> 18.1, >= 18.1.0) 85 | cucumber-messages (~> 15.0, >= 15.0.0) 86 | cucumber-tag-expressions (~> 3.0, >= 3.0.1) 87 | cucumber-create-meta (4.0.0) 88 | cucumber-messages (~> 15.0, >= 15.0.0) 89 | sys-uname (~> 1.2, >= 1.2.2) 90 | cucumber-cucumber-expressions (12.1.1) 91 | cucumber-gherkin (18.1.1) 92 | cucumber-messages (~> 15.0, >= 15.0.0) 93 | cucumber-html-formatter (13.0.0) 94 | cucumber-messages (~> 15.0, >= 15.0.0) 95 | cucumber-messages (15.0.0) 96 | protobuf-cucumber (~> 3.10, >= 3.10.8) 97 | cucumber-tag-expressions (3.0.1) 98 | cucumber-wire (5.0.1) 99 | cucumber-core (~> 9.0, >= 9.0.1) 100 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 101 | cucumber-messages (~> 15.0, >= 15.0.0) 102 | diff-lcs (1.4.4) 103 | dry-initializer (3.0.4) 104 | erubi (1.10.0) 105 | ffi (1.15.3) 106 | globalid (0.4.2) 107 | activesupport (>= 4.2.0) 108 | i18n (1.8.10) 109 | concurrent-ruby (~> 1.0) 110 | loofah (2.10.0) 111 | crass (~> 1.0.2) 112 | nokogiri (>= 1.5.9) 113 | mail (2.7.1) 114 | mini_mime (>= 0.1.1) 115 | marcel (1.0.1) 116 | method_source (1.0.0) 117 | middleware (0.1.0) 118 | mime-types (3.3.1) 119 | mime-types-data (~> 3.2015) 120 | mime-types-data (3.2021.0225) 121 | mini_mime (1.1.0) 122 | mini_portile2 (2.5.3) 123 | minitest (5.14.4) 124 | multi_test (0.1.2) 125 | nio4r (2.5.7) 126 | nokogiri (1.11.7) 127 | mini_portile2 (~> 2.5.0) 128 | racc (~> 1.4) 129 | parallel (1.20.1) 130 | parser (3.0.1.1) 131 | ast (~> 2.4.1) 132 | protobuf-cucumber (3.10.8) 133 | activesupport (>= 3.2) 134 | middleware 135 | thor 136 | thread_safe 137 | racc (1.5.2) 138 | rack (2.2.3) 139 | rack-test (1.1.0) 140 | rack (>= 1.0, < 3) 141 | rails (5.2.6) 142 | actioncable (= 5.2.6) 143 | actionmailer (= 5.2.6) 144 | actionpack (= 5.2.6) 145 | actionview (= 5.2.6) 146 | activejob (= 5.2.6) 147 | activemodel (= 5.2.6) 148 | activerecord (= 5.2.6) 149 | activestorage (= 5.2.6) 150 | activesupport (= 5.2.6) 151 | bundler (>= 1.3.0) 152 | railties (= 5.2.6) 153 | sprockets-rails (>= 2.0.0) 154 | rails-dom-testing (2.0.3) 155 | activesupport (>= 4.2.0) 156 | nokogiri (>= 1.6) 157 | rails-html-sanitizer (1.3.0) 158 | loofah (~> 2.3) 159 | railties (5.2.6) 160 | actionpack (= 5.2.6) 161 | activesupport (= 5.2.6) 162 | method_source 163 | rake (>= 0.8.7) 164 | thor (>= 0.19.0, < 2.0) 165 | rainbow (3.0.0) 166 | rake (13.0.3) 167 | regexp_parser (2.1.1) 168 | rexml (3.2.5) 169 | rspec (3.10.0) 170 | rspec-core (~> 3.10.0) 171 | rspec-expectations (~> 3.10.0) 172 | rspec-mocks (~> 3.10.0) 173 | rspec-core (3.10.1) 174 | rspec-support (~> 3.10.0) 175 | rspec-expectations (3.10.1) 176 | diff-lcs (>= 1.2.0, < 2.0) 177 | rspec-support (~> 3.10.0) 178 | rspec-mocks (3.10.2) 179 | diff-lcs (>= 1.2.0, < 2.0) 180 | rspec-support (~> 3.10.0) 181 | rspec-support (3.10.2) 182 | rubocop (0.93.1) 183 | parallel (~> 1.10) 184 | parser (>= 2.7.1.5) 185 | rainbow (>= 2.2.2, < 4.0) 186 | regexp_parser (>= 1.8) 187 | rexml 188 | rubocop-ast (>= 0.6.0) 189 | ruby-progressbar (~> 1.7) 190 | unicode-display_width (>= 1.4.0, < 2.0) 191 | rubocop-ast (1.7.0) 192 | parser (>= 3.0.1.1) 193 | ruby-progressbar (1.11.0) 194 | sprockets (4.0.2) 195 | concurrent-ruby (~> 1.0) 196 | rack (> 1, < 3) 197 | sprockets-rails (3.2.2) 198 | actionpack (>= 4.0) 199 | activesupport (>= 4.0) 200 | sprockets (>= 3.0.0) 201 | sys-uname (1.2.2) 202 | ffi (~> 1.1) 203 | thor (1.1.0) 204 | thread_safe (0.3.6) 205 | tzinfo (1.2.9) 206 | thread_safe (~> 0.1) 207 | unicode-display_width (1.7.0) 208 | websocket-driver (0.7.5) 209 | websocket-extensions (>= 0.1.0) 210 | websocket-extensions (0.1.5) 211 | 212 | PLATFORMS 213 | ruby 214 | 215 | DEPENDENCIES 216 | appraisal 217 | aruba 218 | bundler (~> 1.17) 219 | cucumber 220 | localer! 221 | rails (~> 5.2) 222 | rake (>= 12.3.3) 223 | rspec (~> 3.0) 224 | rubocop (~> 0.50) 225 | 226 | BUNDLED WITH 227 | 1.17.3 228 | -------------------------------------------------------------------------------- /gemfiles/rails60.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 6.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails60.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (6.1.4) 12 | actionpack (= 6.1.4) 13 | activesupport (= 6.1.4) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | actionmailbox (6.1.4) 17 | actionpack (= 6.1.4) 18 | activejob (= 6.1.4) 19 | activerecord (= 6.1.4) 20 | activestorage (= 6.1.4) 21 | activesupport (= 6.1.4) 22 | mail (>= 2.7.1) 23 | actionmailer (6.1.4) 24 | actionpack (= 6.1.4) 25 | actionview (= 6.1.4) 26 | activejob (= 6.1.4) 27 | activesupport (= 6.1.4) 28 | mail (~> 2.5, >= 2.5.4) 29 | rails-dom-testing (~> 2.0) 30 | actionpack (6.1.4) 31 | actionview (= 6.1.4) 32 | activesupport (= 6.1.4) 33 | rack (~> 2.0, >= 2.0.9) 34 | rack-test (>= 0.6.3) 35 | rails-dom-testing (~> 2.0) 36 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 37 | actiontext (6.1.4) 38 | actionpack (= 6.1.4) 39 | activerecord (= 6.1.4) 40 | activestorage (= 6.1.4) 41 | activesupport (= 6.1.4) 42 | nokogiri (>= 1.8.5) 43 | actionview (6.1.4) 44 | activesupport (= 6.1.4) 45 | builder (~> 3.1) 46 | erubi (~> 1.4) 47 | rails-dom-testing (~> 2.0) 48 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 49 | activejob (6.1.4) 50 | activesupport (= 6.1.4) 51 | globalid (>= 0.3.6) 52 | activemodel (6.1.4) 53 | activesupport (= 6.1.4) 54 | activerecord (6.1.4) 55 | activemodel (= 6.1.4) 56 | activesupport (= 6.1.4) 57 | activestorage (6.1.4) 58 | actionpack (= 6.1.4) 59 | activejob (= 6.1.4) 60 | activerecord (= 6.1.4) 61 | activesupport (= 6.1.4) 62 | marcel (~> 1.0.0) 63 | mini_mime (>= 1.1.0) 64 | activesupport (6.1.4) 65 | concurrent-ruby (~> 1.0, >= 1.0.2) 66 | i18n (>= 1.6, < 2) 67 | minitest (>= 5.1) 68 | tzinfo (~> 2.0) 69 | zeitwerk (~> 2.3) 70 | appraisal (2.4.0) 71 | bundler 72 | rake 73 | thor (>= 0.14.0) 74 | aruba (1.1.2) 75 | bundler (>= 1.17, < 3.0) 76 | childprocess (>= 2.0, < 5.0) 77 | contracts (>= 0.16.0, < 0.18.0) 78 | cucumber (>= 2.4, < 7.0) 79 | rspec-expectations (~> 3.4) 80 | thor (~> 1.0) 81 | ast (2.4.2) 82 | builder (3.2.4) 83 | childprocess (4.1.0) 84 | concurrent-ruby (1.1.9) 85 | contracts (0.16.1) 86 | crass (1.0.6) 87 | cucumber (6.1.0) 88 | builder (~> 3.2, >= 3.2.4) 89 | cucumber-core (~> 9.0, >= 9.0.1) 90 | cucumber-create-meta (~> 4.0, >= 4.0.0) 91 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 92 | cucumber-gherkin (~> 18.1, >= 18.1.0) 93 | cucumber-html-formatter (~> 13.0, >= 13.0.0) 94 | cucumber-messages (~> 15.0, >= 15.0.0) 95 | cucumber-wire (~> 5.0, >= 5.0.1) 96 | diff-lcs (~> 1.4, >= 1.4.4) 97 | mime-types (~> 3.3, >= 3.3.1) 98 | multi_test (~> 0.1, >= 0.1.2) 99 | sys-uname (~> 1.2, >= 1.2.2) 100 | cucumber-core (9.0.1) 101 | cucumber-gherkin (~> 18.1, >= 18.1.0) 102 | cucumber-messages (~> 15.0, >= 15.0.0) 103 | cucumber-tag-expressions (~> 3.0, >= 3.0.1) 104 | cucumber-create-meta (4.0.0) 105 | cucumber-messages (~> 15.0, >= 15.0.0) 106 | sys-uname (~> 1.2, >= 1.2.2) 107 | cucumber-cucumber-expressions (12.1.1) 108 | cucumber-gherkin (18.1.1) 109 | cucumber-messages (~> 15.0, >= 15.0.0) 110 | cucumber-html-formatter (13.0.0) 111 | cucumber-messages (~> 15.0, >= 15.0.0) 112 | cucumber-messages (15.0.0) 113 | protobuf-cucumber (~> 3.10, >= 3.10.8) 114 | cucumber-tag-expressions (3.0.1) 115 | cucumber-wire (5.0.1) 116 | cucumber-core (~> 9.0, >= 9.0.1) 117 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 118 | cucumber-messages (~> 15.0, >= 15.0.0) 119 | diff-lcs (1.4.4) 120 | dry-initializer (3.0.4) 121 | erubi (1.10.0) 122 | ffi (1.15.3) 123 | globalid (0.4.2) 124 | activesupport (>= 4.2.0) 125 | i18n (1.8.10) 126 | concurrent-ruby (~> 1.0) 127 | loofah (2.10.0) 128 | crass (~> 1.0.2) 129 | nokogiri (>= 1.5.9) 130 | mail (2.7.1) 131 | mini_mime (>= 0.1.1) 132 | marcel (1.0.1) 133 | method_source (1.0.0) 134 | middleware (0.1.0) 135 | mime-types (3.3.1) 136 | mime-types-data (~> 3.2015) 137 | mime-types-data (3.2021.0225) 138 | mini_mime (1.1.0) 139 | mini_portile2 (2.5.3) 140 | minitest (5.14.4) 141 | multi_test (0.1.2) 142 | nio4r (2.5.7) 143 | nokogiri (1.11.7) 144 | mini_portile2 (~> 2.5.0) 145 | racc (~> 1.4) 146 | parallel (1.20.1) 147 | parser (3.0.1.1) 148 | ast (~> 2.4.1) 149 | protobuf-cucumber (3.10.8) 150 | activesupport (>= 3.2) 151 | middleware 152 | thor 153 | thread_safe 154 | racc (1.5.2) 155 | rack (2.2.3) 156 | rack-test (1.1.0) 157 | rack (>= 1.0, < 3) 158 | rails (6.1.4) 159 | actioncable (= 6.1.4) 160 | actionmailbox (= 6.1.4) 161 | actionmailer (= 6.1.4) 162 | actionpack (= 6.1.4) 163 | actiontext (= 6.1.4) 164 | actionview (= 6.1.4) 165 | activejob (= 6.1.4) 166 | activemodel (= 6.1.4) 167 | activerecord (= 6.1.4) 168 | activestorage (= 6.1.4) 169 | activesupport (= 6.1.4) 170 | bundler (>= 1.15.0) 171 | railties (= 6.1.4) 172 | sprockets-rails (>= 2.0.0) 173 | rails-dom-testing (2.0.3) 174 | activesupport (>= 4.2.0) 175 | nokogiri (>= 1.6) 176 | rails-html-sanitizer (1.3.0) 177 | loofah (~> 2.3) 178 | railties (6.1.4) 179 | actionpack (= 6.1.4) 180 | activesupport (= 6.1.4) 181 | method_source 182 | rake (>= 0.13) 183 | thor (~> 1.0) 184 | rainbow (3.0.0) 185 | rake (13.0.3) 186 | regexp_parser (2.1.1) 187 | rexml (3.2.5) 188 | rspec (3.10.0) 189 | rspec-core (~> 3.10.0) 190 | rspec-expectations (~> 3.10.0) 191 | rspec-mocks (~> 3.10.0) 192 | rspec-core (3.10.1) 193 | rspec-support (~> 3.10.0) 194 | rspec-expectations (3.10.1) 195 | diff-lcs (>= 1.2.0, < 2.0) 196 | rspec-support (~> 3.10.0) 197 | rspec-mocks (3.10.2) 198 | diff-lcs (>= 1.2.0, < 2.0) 199 | rspec-support (~> 3.10.0) 200 | rspec-support (3.10.2) 201 | rubocop (0.93.1) 202 | parallel (~> 1.10) 203 | parser (>= 2.7.1.5) 204 | rainbow (>= 2.2.2, < 4.0) 205 | regexp_parser (>= 1.8) 206 | rexml 207 | rubocop-ast (>= 0.6.0) 208 | ruby-progressbar (~> 1.7) 209 | unicode-display_width (>= 1.4.0, < 2.0) 210 | rubocop-ast (1.7.0) 211 | parser (>= 3.0.1.1) 212 | ruby-progressbar (1.11.0) 213 | sprockets (4.0.2) 214 | concurrent-ruby (~> 1.0) 215 | rack (> 1, < 3) 216 | sprockets-rails (3.2.2) 217 | actionpack (>= 4.0) 218 | activesupport (>= 4.0) 219 | sprockets (>= 3.0.0) 220 | sys-uname (1.2.2) 221 | ffi (~> 1.1) 222 | thor (1.1.0) 223 | thread_safe (0.3.6) 224 | tzinfo (2.0.4) 225 | concurrent-ruby (~> 1.0) 226 | unicode-display_width (1.7.0) 227 | websocket-driver (0.7.5) 228 | websocket-extensions (>= 0.1.0) 229 | websocket-extensions (0.1.5) 230 | zeitwerk (2.4.2) 231 | 232 | PLATFORMS 233 | ruby 234 | 235 | DEPENDENCIES 236 | appraisal 237 | aruba 238 | bundler (~> 1.17) 239 | cucumber 240 | localer! 241 | rails (~> 6.0) 242 | rake (>= 12.3.3) 243 | rspec (~> 3.0) 244 | rubocop (~> 0.50) 245 | 246 | BUNDLED WITH 247 | 1.17.3 248 | -------------------------------------------------------------------------------- /gemfiles/rails61.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 6.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails61.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | localer (0.2.0) 5 | dry-initializer (>= 2.0) 6 | thor (>= 0.19) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (6.1.4) 12 | actionpack (= 6.1.4) 13 | activesupport (= 6.1.4) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | actionmailbox (6.1.4) 17 | actionpack (= 6.1.4) 18 | activejob (= 6.1.4) 19 | activerecord (= 6.1.4) 20 | activestorage (= 6.1.4) 21 | activesupport (= 6.1.4) 22 | mail (>= 2.7.1) 23 | actionmailer (6.1.4) 24 | actionpack (= 6.1.4) 25 | actionview (= 6.1.4) 26 | activejob (= 6.1.4) 27 | activesupport (= 6.1.4) 28 | mail (~> 2.5, >= 2.5.4) 29 | rails-dom-testing (~> 2.0) 30 | actionpack (6.1.4) 31 | actionview (= 6.1.4) 32 | activesupport (= 6.1.4) 33 | rack (~> 2.0, >= 2.0.9) 34 | rack-test (>= 0.6.3) 35 | rails-dom-testing (~> 2.0) 36 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 37 | actiontext (6.1.4) 38 | actionpack (= 6.1.4) 39 | activerecord (= 6.1.4) 40 | activestorage (= 6.1.4) 41 | activesupport (= 6.1.4) 42 | nokogiri (>= 1.8.5) 43 | actionview (6.1.4) 44 | activesupport (= 6.1.4) 45 | builder (~> 3.1) 46 | erubi (~> 1.4) 47 | rails-dom-testing (~> 2.0) 48 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 49 | activejob (6.1.4) 50 | activesupport (= 6.1.4) 51 | globalid (>= 0.3.6) 52 | activemodel (6.1.4) 53 | activesupport (= 6.1.4) 54 | activerecord (6.1.4) 55 | activemodel (= 6.1.4) 56 | activesupport (= 6.1.4) 57 | activestorage (6.1.4) 58 | actionpack (= 6.1.4) 59 | activejob (= 6.1.4) 60 | activerecord (= 6.1.4) 61 | activesupport (= 6.1.4) 62 | marcel (~> 1.0.0) 63 | mini_mime (>= 1.1.0) 64 | activesupport (6.1.4) 65 | concurrent-ruby (~> 1.0, >= 1.0.2) 66 | i18n (>= 1.6, < 2) 67 | minitest (>= 5.1) 68 | tzinfo (~> 2.0) 69 | zeitwerk (~> 2.3) 70 | appraisal (2.4.0) 71 | bundler 72 | rake 73 | thor (>= 0.14.0) 74 | aruba (1.1.2) 75 | bundler (>= 1.17, < 3.0) 76 | childprocess (>= 2.0, < 5.0) 77 | contracts (>= 0.16.0, < 0.18.0) 78 | cucumber (>= 2.4, < 7.0) 79 | rspec-expectations (~> 3.4) 80 | thor (~> 1.0) 81 | ast (2.4.2) 82 | builder (3.2.4) 83 | childprocess (4.1.0) 84 | concurrent-ruby (1.1.9) 85 | contracts (0.16.1) 86 | crass (1.0.6) 87 | cucumber (6.1.0) 88 | builder (~> 3.2, >= 3.2.4) 89 | cucumber-core (~> 9.0, >= 9.0.1) 90 | cucumber-create-meta (~> 4.0, >= 4.0.0) 91 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 92 | cucumber-gherkin (~> 18.1, >= 18.1.0) 93 | cucumber-html-formatter (~> 13.0, >= 13.0.0) 94 | cucumber-messages (~> 15.0, >= 15.0.0) 95 | cucumber-wire (~> 5.0, >= 5.0.1) 96 | diff-lcs (~> 1.4, >= 1.4.4) 97 | mime-types (~> 3.3, >= 3.3.1) 98 | multi_test (~> 0.1, >= 0.1.2) 99 | sys-uname (~> 1.2, >= 1.2.2) 100 | cucumber-core (9.0.1) 101 | cucumber-gherkin (~> 18.1, >= 18.1.0) 102 | cucumber-messages (~> 15.0, >= 15.0.0) 103 | cucumber-tag-expressions (~> 3.0, >= 3.0.1) 104 | cucumber-create-meta (4.0.0) 105 | cucumber-messages (~> 15.0, >= 15.0.0) 106 | sys-uname (~> 1.2, >= 1.2.2) 107 | cucumber-cucumber-expressions (12.1.1) 108 | cucumber-gherkin (18.1.1) 109 | cucumber-messages (~> 15.0, >= 15.0.0) 110 | cucumber-html-formatter (13.0.0) 111 | cucumber-messages (~> 15.0, >= 15.0.0) 112 | cucumber-messages (15.0.0) 113 | protobuf-cucumber (~> 3.10, >= 3.10.8) 114 | cucumber-tag-expressions (3.0.1) 115 | cucumber-wire (5.0.1) 116 | cucumber-core (~> 9.0, >= 9.0.1) 117 | cucumber-cucumber-expressions (~> 12.1, >= 12.1.1) 118 | cucumber-messages (~> 15.0, >= 15.0.0) 119 | diff-lcs (1.4.4) 120 | dry-initializer (3.0.4) 121 | erubi (1.10.0) 122 | ffi (1.15.3) 123 | globalid (0.4.2) 124 | activesupport (>= 4.2.0) 125 | i18n (1.8.10) 126 | concurrent-ruby (~> 1.0) 127 | loofah (2.10.0) 128 | crass (~> 1.0.2) 129 | nokogiri (>= 1.5.9) 130 | mail (2.7.1) 131 | mini_mime (>= 0.1.1) 132 | marcel (1.0.1) 133 | method_source (1.0.0) 134 | middleware (0.1.0) 135 | mime-types (3.3.1) 136 | mime-types-data (~> 3.2015) 137 | mime-types-data (3.2021.0225) 138 | mini_mime (1.1.0) 139 | mini_portile2 (2.5.3) 140 | minitest (5.14.4) 141 | multi_test (0.1.2) 142 | nio4r (2.5.7) 143 | nokogiri (1.11.7) 144 | mini_portile2 (~> 2.5.0) 145 | racc (~> 1.4) 146 | parallel (1.20.1) 147 | parser (3.0.1.1) 148 | ast (~> 2.4.1) 149 | protobuf-cucumber (3.10.8) 150 | activesupport (>= 3.2) 151 | middleware 152 | thor 153 | thread_safe 154 | racc (1.5.2) 155 | rack (2.2.3) 156 | rack-test (1.1.0) 157 | rack (>= 1.0, < 3) 158 | rails (6.1.4) 159 | actioncable (= 6.1.4) 160 | actionmailbox (= 6.1.4) 161 | actionmailer (= 6.1.4) 162 | actionpack (= 6.1.4) 163 | actiontext (= 6.1.4) 164 | actionview (= 6.1.4) 165 | activejob (= 6.1.4) 166 | activemodel (= 6.1.4) 167 | activerecord (= 6.1.4) 168 | activestorage (= 6.1.4) 169 | activesupport (= 6.1.4) 170 | bundler (>= 1.15.0) 171 | railties (= 6.1.4) 172 | sprockets-rails (>= 2.0.0) 173 | rails-dom-testing (2.0.3) 174 | activesupport (>= 4.2.0) 175 | nokogiri (>= 1.6) 176 | rails-html-sanitizer (1.3.0) 177 | loofah (~> 2.3) 178 | railties (6.1.4) 179 | actionpack (= 6.1.4) 180 | activesupport (= 6.1.4) 181 | method_source 182 | rake (>= 0.13) 183 | thor (~> 1.0) 184 | rainbow (3.0.0) 185 | rake (13.0.3) 186 | regexp_parser (2.1.1) 187 | rexml (3.2.5) 188 | rspec (3.10.0) 189 | rspec-core (~> 3.10.0) 190 | rspec-expectations (~> 3.10.0) 191 | rspec-mocks (~> 3.10.0) 192 | rspec-core (3.10.1) 193 | rspec-support (~> 3.10.0) 194 | rspec-expectations (3.10.1) 195 | diff-lcs (>= 1.2.0, < 2.0) 196 | rspec-support (~> 3.10.0) 197 | rspec-mocks (3.10.2) 198 | diff-lcs (>= 1.2.0, < 2.0) 199 | rspec-support (~> 3.10.0) 200 | rspec-support (3.10.2) 201 | rubocop (0.93.1) 202 | parallel (~> 1.10) 203 | parser (>= 2.7.1.5) 204 | rainbow (>= 2.2.2, < 4.0) 205 | regexp_parser (>= 1.8) 206 | rexml 207 | rubocop-ast (>= 0.6.0) 208 | ruby-progressbar (~> 1.7) 209 | unicode-display_width (>= 1.4.0, < 2.0) 210 | rubocop-ast (1.7.0) 211 | parser (>= 3.0.1.1) 212 | ruby-progressbar (1.11.0) 213 | sprockets (4.0.2) 214 | concurrent-ruby (~> 1.0) 215 | rack (> 1, < 3) 216 | sprockets-rails (3.2.2) 217 | actionpack (>= 4.0) 218 | activesupport (>= 4.0) 219 | sprockets (>= 3.0.0) 220 | sys-uname (1.2.2) 221 | ffi (~> 1.1) 222 | thor (1.1.0) 223 | thread_safe (0.3.6) 224 | tzinfo (2.0.4) 225 | concurrent-ruby (~> 1.0) 226 | unicode-display_width (1.7.0) 227 | websocket-driver (0.7.5) 228 | websocket-extensions (>= 0.1.0) 229 | websocket-extensions (0.1.5) 230 | zeitwerk (2.4.2) 231 | 232 | PLATFORMS 233 | ruby 234 | 235 | DEPENDENCIES 236 | appraisal 237 | aruba 238 | bundler (~> 1.17) 239 | cucumber 240 | localer! 241 | rails (~> 6.1) 242 | rake (>= 12.3.3) 243 | rspec (~> 3.0) 244 | rubocop (~> 0.50) 245 | 246 | BUNDLED WITH 247 | 1.17.3 248 | -------------------------------------------------------------------------------- /lib/localer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "dry-initializer" 4 | require_relative "localer/version" 5 | require_relative "localer/rails" 6 | require_relative "localer/config" 7 | require_relative "localer/data" 8 | 9 | module Localer # :nodoc: 10 | using Localer::Ext::Hash 11 | # using Localer::Ext::String 12 | 13 | class << self 14 | def data 15 | @data ||= load_data 16 | end 17 | 18 | def config 19 | @config ||= configure 20 | end 21 | 22 | def configure(options = {}) 23 | @config = Config.load(options) 24 | end 25 | 26 | def load_data(source = Localer::Rails.translations) 27 | @data = Data.new(source) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/localer/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | require_relative '../localer/ext/hash' 5 | require_relative 'config/locale' 6 | 7 | module Localer # :nodoc: 8 | using Localer::Ext::Hash 9 | 10 | # Loads and parse Localer config file `.localer.yml` 11 | class Config 12 | extend Dry::Initializer 13 | 14 | APP_PATH = Dir.pwd 15 | CONFIG_FILENAME = ".localer.yml" 16 | 17 | option :exclude, default: -> { [] } 18 | option :locale, proc { |hash| parse_locales(hash) }, default: -> { Hash.new(Locale.new) } 19 | option :app_path, default: -> { APP_PATH } 20 | 21 | class << self 22 | def load(options = {}) 23 | opts = options.deep_symbolize_keys 24 | app_path = opts.fetch(:app_path, APP_PATH) 25 | file_options = file_config(CONFIG_FILENAME, app_path) 26 | new(file_options.deep_merge(opts).deep_symbolize_keys) 27 | end 28 | 29 | def file_config(filename, path) 30 | filename = File.expand_path(filename, path) 31 | return {} unless File.exist?(filename) 32 | return {} if File.zero?(filename) 33 | 34 | YAML 35 | .load_file(filename) 36 | .deep_downcase_keys 37 | .deep_symbolize_keys 38 | end 39 | 40 | def parse_locales(hash) 41 | hash.each_with_object(Hash.new(Locale.new)) do |(l, v), h| 42 | h[l] = Locale.new(v) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/localer/config/locale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | class Config 5 | # Provide config for locale 6 | class Locale 7 | extend Dry::Initializer 8 | option :exclude, default: -> { [] } 9 | option :enabled, default: -> { true } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/localer/data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "data/service" 4 | require_relative "data/checker" 5 | require_relative "data/processor" 6 | require_relative "data/missing_translations" 7 | 8 | module Localer 9 | # Stores translations and provides 10 | # check methods 11 | class Data 12 | extend Dry::Initializer 13 | param :source, default: -> { {} } 14 | param :config, default: -> { Localer.config } 15 | 16 | attr_reader :translations, :locales 17 | 18 | def initialize(*args) 19 | super 20 | @locales, @translations = Processor.call(source, config) 21 | end 22 | 23 | def complete? 24 | Checker.call(self) 25 | end 26 | 27 | def missing_translations 28 | MissingTranslations.call(self) 29 | end 30 | 31 | def each 32 | @translations.each do |key, value| 33 | @locales.each do |locale| 34 | yield locale, key, value[locale] 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/localer/data/checker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | class Data 5 | # Check missing translations 6 | # Returns true if no missing translations found, otherwise false 7 | class Checker < Service 8 | param :data 9 | 10 | def call 11 | data.each do |_locale, _key, value| 12 | return false if value.nil? 13 | end 14 | true 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/localer/data/missing_translations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | class Data 5 | # A service that returns array of missing translations 6 | class MissingTranslations < Service 7 | param :data 8 | 9 | def call 10 | missing = [] 11 | data.each do |locale, key, value| 12 | missing.push("#{locale}#{key}") if value.nil? 13 | end 14 | missing 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/localer/data/processor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../ext/string' 4 | module Localer # :nodoc: 5 | using Localer::Ext::String 6 | 7 | class Data 8 | # Parse translations into hash: 9 | # key: translation key 10 | # value: hash of locale values 11 | class Processor < Service 12 | param :translations 13 | param :config, default: -> { Localer.config } 14 | 15 | attr_reader :data, :locales 16 | 17 | def call 18 | @data = Hash.new { |hsh, key| hsh[key] = {} } 19 | @locales = [] 20 | translations.each do |(locale, translation)| 21 | next unless config.locale[locale.downcase].enabled 22 | 23 | @locales.push locale 24 | prepare(locale, translation) 25 | end 26 | [@locales, @data] 27 | end 28 | 29 | private 30 | 31 | def prepare(locale, translation, prefix = "") 32 | if translation.is_a?(Hash) 33 | translation.each do |(key, value)| 34 | full_key = prefix + ".#{key}" 35 | next if exclude?(full_key, locale) 36 | 37 | prepare(locale, value, full_key) 38 | end 39 | else 40 | # @data[prefix] ||= {} 41 | @data[prefix][locale] = translation 42 | end 43 | end 44 | 45 | def exclude?(key, locale) 46 | (config.exclude + config.locale[locale.downcase].exclude).any? do |pattern| 47 | match?(key, pattern) 48 | end 49 | end 50 | 51 | def match?(key, pattern) 52 | if (regex = pattern.to_regexp) 53 | key =~ regex 54 | else 55 | key.start_with?(pattern) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/localer/data/service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry-initializer' 4 | module Localer 5 | class Data 6 | # Core service object 7 | class Service 8 | extend Dry::Initializer # use `param` and `option` for dependencies 9 | 10 | class << self 11 | # Instantiates and calls the service at once 12 | def call(*args, &block) 13 | new(*args).call(&block) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/localer/ext/hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | module Ext 5 | # Extend Hash through refinements 6 | module Hash 7 | refine ::Hash do 8 | # From ActiveSupport http://api.rubyonrails.org/classes/Hash.html#metho 9 | def deep_merge!(other_hash) 10 | other_hash.each_pair do |current_key, other_value| 11 | this_value = self[current_key] 12 | 13 | if this_value.is_a?(::Hash) && other_value.is_a?(::Hash) 14 | this_value.deep_merge!(other_value) 15 | this_value 16 | else 17 | self[current_key] = other_value 18 | end 19 | end 20 | 21 | self 22 | end 23 | 24 | def deep_merge(other_hash, &block) 25 | dup.deep_merge!(other_hash, &block) 26 | end 27 | 28 | def deep_symbolize_keys 29 | deep_transform_keys do |key| 30 | begin 31 | key.to_sym 32 | rescue StandardError 33 | key 34 | end 35 | end 36 | end 37 | 38 | def deep_downcase_keys 39 | deep_transform_keys do |key| 40 | begin 41 | key.downcase 42 | rescue StandardError 43 | key 44 | end 45 | end 46 | end 47 | 48 | def deep_transform_keys(&block) 49 | _deep_transform_keys_in_object(self, &block) 50 | end 51 | 52 | def deep_transform_keys!(&block) 53 | _deep_transform_keys_in_object!(self, &block) 54 | end 55 | 56 | private 57 | 58 | def _deep_transform_keys_in_object!(object, &block) 59 | case object 60 | when ::Hash 61 | object.keys.each do |key| 62 | value = object.delete(key) 63 | object[yield(key)] = _deep_transform_keys_in_object!(value, &block) 64 | end 65 | object 66 | when Array 67 | object.map! { |e| _deep_transform_keys_in_object!(e, &block) } 68 | else 69 | object 70 | end 71 | end 72 | 73 | # support methods for deep transforming nested hashes and arrays 74 | def _deep_transform_keys_in_object(object, &block) 75 | case object 76 | when ::Hash 77 | object.each_with_object({}) do |(key, value), result| 78 | result[yield(key)] = _deep_transform_keys_in_object(value, &block) 79 | end 80 | when Array 81 | object.map { |e| _deep_transform_keys_in_object(e, &block) } 82 | else 83 | object 84 | end 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/localer/ext/string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | module Ext 5 | # Extend Hash through refinements 6 | # taken from https://github.com/seamusabshere/to_regexp 7 | module String 8 | INLINE_OPTIONS = /[imxnesu]*/ 9 | REGEXP_DELIMITERS = { 10 | '%r{' => '}', 11 | '/' => '/' 12 | }.freeze 13 | 14 | refine ::String do 15 | def literal? 16 | REGEXP_DELIMITERS.none? { |s, e| start_with?(s) && self =~ /#{e}#{INLINE_OPTIONS}\z/ } 17 | end 18 | 19 | def to_regexp(options = {}) 20 | if args = as_regexp(options) 21 | ::Regexp.new(*args) 22 | end 23 | end 24 | 25 | def as_regexp(options = {}) 26 | raise ::ArgumentError, "[to_regexp] Options must be a Hash" unless options.is_a?(::Hash) 27 | str = self 28 | 29 | return if options[:detect] && (str == '') 30 | 31 | if options[:literal] || (options[:detect] && str.literal?) 32 | content = ::Regexp.escape str 33 | elsif delim_set = REGEXP_DELIMITERS.detect { |k, _| str.start_with?(k) } 34 | delim_start, delim_end = delim_set 35 | /\A#{delim_start}(.*)#{delim_end}(#{INLINE_OPTIONS})\z/u =~ str 36 | content = Regexp.last_match(1) 37 | inline_options = Regexp.last_match(2) 38 | return unless content.is_a?(::String) 39 | content.gsub! '\\/', '/' 40 | if inline_options 41 | options[:ignore_case] = true if inline_options.include?('i') 42 | options[:multiline] = true if inline_options.include?('m') 43 | options[:extended] = true if inline_options.include?('x') 44 | # 'n', 'N' = none, 'e', 'E' = EUC, 's', 'S' = SJIS, 'u', 'U' = UTF-8 45 | options[:lang] = inline_options.scan(/[nesu]/i).join.downcase 46 | end 47 | else 48 | return 49 | end 50 | 51 | ignore_case = options[:ignore_case] ? ::Regexp::IGNORECASE : 0 52 | multiline = options[:multiline] ? ::Regexp::MULTILINE : 0 53 | extended = options[:extended] ? ::Regexp::EXTENDED : 0 54 | lang = options[:lang] || '' 55 | lang = lang.delete 'u' if (::RUBY_VERSION > '1.9') && lang.include?('u') 56 | 57 | if lang.empty? 58 | [content, (ignore_case | multiline | extended)] 59 | else 60 | [content, (ignore_case | multiline | extended), lang] 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/localer/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | module Rails # :nodoc: 5 | class << self 6 | def connect! 7 | require File.expand_path("config/environment", Localer.config.app_path) 8 | true 9 | rescue LoadError 10 | false 11 | end 12 | 13 | def translations 14 | return {} unless connect! 15 | 16 | I18n.backend.send(:init_translations) 17 | I18n.backend.send(:translations) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/localer/rake_task.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake' 4 | require 'rake/tasklib' 5 | require 'localer' 6 | 7 | module Localer 8 | # Defines a Rake task for running Localer. 9 | # The simplest use of it goes something like: 10 | # 11 | # Localer::Rakeask.new 12 | # This will define a task named localer described as 'Run Localer'. 13 | class RakeTask < Rake::TaskLib 14 | def initialize(name = :localer, *args) # rubocop:disable Lint/MissingSuper 15 | @name = name 16 | desc 'Run Localer' 17 | task(name, *args) do |_, _task_args| 18 | sh('localer check') do |ok, res| 19 | exit res.exitstatus unless ok 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/localer/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Localer 4 | VERSION = "0.2.0" 5 | end 6 | -------------------------------------------------------------------------------- /localer.gemspec: -------------------------------------------------------------------------------- 1 | 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "localer/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "localer" 8 | spec.version = Localer::VERSION 9 | spec.authors = ["Andrey Deryabin"] 10 | spec.email = ["deriabin@gmail.com"] 11 | 12 | spec.summary = %q{Automatic detecting missing I18n translations tool.} 13 | spec.description = %q{Automatic detecting missing I18n translations tool.} 14 | spec.homepage = "https://github.com/aderyabin/localer" 15 | spec.license = "MIT" 16 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 17 | f.match(%r{^(test|spec|features|gemfiles)/}) 18 | end 19 | spec.bindir = "bin" 20 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_development_dependency "appraisal" 24 | spec.add_development_dependency "bundler", "~> 1.17" 25 | spec.add_development_dependency "rake", ">= 12.3.3" 26 | spec.add_development_dependency "rspec", "~> 3.0" 27 | spec.add_development_dependency "rubocop", "~> 0.50" 28 | spec.add_development_dependency "cucumber" 29 | spec.add_development_dependency "aruba" 30 | 31 | spec.add_dependency "thor", ">= 0.19" 32 | spec.add_dependency "dry-initializer", ">= 2.0" 33 | end 34 | -------------------------------------------------------------------------------- /public/localer-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aderyabin/localer/4622d727ab40e6cd0365cfc72d5ddb25abe06edc/public/localer-full.png -------------------------------------------------------------------------------- /public/localer-full2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aderyabin/localer/4622d727ab40e6cd0365cfc72d5ddb25abe06edc/public/localer-full2.png -------------------------------------------------------------------------------- /public/localer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aderyabin/localer/4622d727ab40e6cd0365cfc72d5ddb25abe06edc/public/localer-logo.png -------------------------------------------------------------------------------- /spec/dummy_app/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require ::File.expand_path("../config/environment", __FILE__) 4 | 5 | Rails.application.eager_load! 6 | 7 | run Rails.application 8 | -------------------------------------------------------------------------------- /spec/dummy_app/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails" 4 | 5 | class DummyApp < Rails::Application 6 | config.eager_load = false 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require File.expand_path('application', __dir__) 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/en.rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | { 4 | en: { 5 | number: { 6 | nth: { 7 | ordinals: lambda do |_key, options| 8 | number = options[:number] 9 | case number 10 | when 1 then "st" 11 | when 2 then "nd" 12 | when 3 then "rd" 13 | when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 then "th" 14 | else 15 | num_modulo = number.to_i.abs % 100 16 | num_modulo %= 10 if num_modulo > 13 17 | case num_modulo 18 | when 1 then "st" 19 | when 2 then "nd" 20 | when 3 then "rd" 21 | else "th" 22 | end 23 | end 24 | end, 25 | 26 | ordinalized: lambda do |_key, options| 27 | number = options[:number] 28 | "#{number}#{ActiveSupport::Inflector.ordinal(number)}" 29 | end 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/en.rails.yml: -------------------------------------------------------------------------------- 1 | en: 2 | # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() 3 | datetime: 4 | distance_in_words: 5 | half_a_minute: "half a minute" 6 | less_than_x_seconds: 7 | one: "less than 1 second" 8 | other: "less than %{count} seconds" 9 | x_seconds: 10 | one: "1 second" 11 | other: "%{count} seconds" 12 | less_than_x_minutes: 13 | one: "less than a minute" 14 | other: "less than %{count} minutes" 15 | x_minutes: 16 | one: "1 minute" 17 | other: "%{count} minutes" 18 | about_x_hours: 19 | one: "about 1 hour" 20 | other: "about %{count} hours" 21 | x_days: 22 | one: "1 day" 23 | other: "%{count} days" 24 | about_x_months: 25 | one: "about 1 month" 26 | other: "about %{count} months" 27 | x_months: 28 | one: "1 month" 29 | other: "%{count} months" 30 | about_x_years: 31 | one: "about 1 year" 32 | other: "about %{count} years" 33 | over_x_years: 34 | one: "over 1 year" 35 | other: "over %{count} years" 36 | almost_x_years: 37 | one: "almost 1 year" 38 | other: "almost %{count} years" 39 | prompts: 40 | year: "Year" 41 | month: "Month" 42 | day: "Day" 43 | hour: "Hour" 44 | minute: "Minute" 45 | second: "Seconds" 46 | 47 | helpers: 48 | select: 49 | # Default value for :prompt => true in FormOptionsHelper 50 | prompt: "Please select" 51 | 52 | # Default translation keys for submit and button FormHelper 53 | submit: 54 | create: 'Create %{model}' 55 | update: 'Update %{model}' 56 | submit: 'Save %{model}' 57 | date: 58 | formats: 59 | # Use the strftime parameters for formats. 60 | # When no format has been given, it uses default. 61 | # You can provide other formats here if you like! 62 | default: "%Y-%m-%d" 63 | short: "%b %d" 64 | long: "%B %d, %Y" 65 | 66 | day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] 67 | abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] 68 | 69 | # Don't forget the nil at the beginning; there's no such thing as a 0th month 70 | month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] 71 | abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] 72 | # Used in date_select and datetime_select. 73 | order: 74 | - year 75 | - month 76 | - day 77 | 78 | time: 79 | formats: 80 | default: "%a, %d %b %Y %H:%M:%S %z" 81 | short: "%d %b %H:%M" 82 | long: "%B %d, %Y %H:%M" 83 | am: "am" 84 | pm: "pm" 85 | 86 | # Used in array.to_sentence. 87 | support: 88 | array: 89 | words_connector: ", " 90 | two_words_connector: " and " 91 | last_word_connector: ", and " 92 | number: 93 | # Used in NumberHelper.number_to_delimited() 94 | # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' 95 | format: 96 | # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) 97 | separator: "." 98 | # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) 99 | delimiter: "," 100 | # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) 101 | precision: 3 102 | # Determine how rounding is performed (see BigDecimal::mode) 103 | round_mode: default 104 | # If set to true, precision will mean the number of significant digits instead 105 | # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) 106 | significant: false 107 | # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) 108 | strip_insignificant_zeros: false 109 | 110 | # Used in NumberHelper.number_to_currency() 111 | currency: 112 | format: 113 | # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) 114 | format: "%u%n" 115 | unit: "$" 116 | # These five are to override number.format and are optional 117 | separator: "." 118 | delimiter: "," 119 | precision: 2 120 | significant: false 121 | strip_insignificant_zeros: false 122 | 123 | # Used in NumberHelper.number_to_percentage() 124 | percentage: 125 | format: 126 | # These five are to override number.format and are optional 127 | # separator: 128 | delimiter: "" 129 | # precision: 130 | # significant: false 131 | # strip_insignificant_zeros: false 132 | format: "%n%" 133 | 134 | # Used in NumberHelper.number_to_rounded() 135 | precision: 136 | format: 137 | # These five are to override number.format and are optional 138 | # separator: 139 | delimiter: "" 140 | # precision: 141 | # significant: false 142 | # strip_insignificant_zeros: false 143 | 144 | # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() 145 | human: 146 | format: 147 | # These five are to override number.format and are optional 148 | # separator: 149 | delimiter: "" 150 | precision: 3 151 | significant: true 152 | strip_insignificant_zeros: true 153 | # Used in number_to_human_size() 154 | storage_units: 155 | # Storage units output formatting. 156 | # %u is the storage unit, %n is the number (default: 2 MB) 157 | format: "%n %u" 158 | units: 159 | byte: 160 | one: "Byte" 161 | other: "Bytes" 162 | kb: "KB" 163 | mb: "MB" 164 | gb: "GB" 165 | tb: "TB" 166 | pb: "PB" 167 | eb: "EB" 168 | # Used in NumberHelper.number_to_human() 169 | decimal_units: 170 | format: "%n %u" 171 | # Decimal units output formatting 172 | # By default we will only quantify some of the exponents 173 | # but the commented ones might be defined or overridden 174 | # by the user. 175 | units: 176 | # femto: Quadrillionth 177 | # pico: Trillionth 178 | # nano: Billionth 179 | # micro: Millionth 180 | # mili: Thousandth 181 | # centi: Hundredth 182 | # deci: Tenth 183 | unit: "" 184 | # ten: 185 | # one: Ten 186 | # other: Tens 187 | # hundred: Hundred 188 | thousand: Thousand 189 | million: Million 190 | billion: Billion 191 | trillion: Trillion 192 | quadrillion: Quadrillion 193 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/ru.rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | { 4 | ru: { 5 | number: { 6 | nth: { 7 | ordinals: ->(_key, _options) {}, 8 | ordinalized: ->(_key, options) {} 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/ru.rails.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | date: 3 | abbr_day_names: 4 | - 5 | abbr_month_names: 6 | - 7 | day_names: 8 | - 9 | month_names: 10 | - 11 | order: 12 | - 13 | formats: 14 | default: "%d.%m.%Y" 15 | long: "%-d %B %Y" 16 | short: "%-d %b" 17 | time: 18 | am: утра 19 | formats: 20 | default: "%a, %d %b %Y, %H:%M:%S %z" 21 | long: "%d %B %Y, %H:%M" 22 | short: "%d %b, %H:%M" 23 | pm: вечера 24 | datetime: 25 | prompts: 26 | day: День 27 | hour: Часов 28 | minute: Минут 29 | month: Месяц 30 | second: Секунд 31 | year: Год 32 | distance_in_words: 33 | about_x_hours: 34 | one: около %{count} часа 35 | other: около %{count} часа 36 | about_x_months: 37 | one: около %{count} месяца 38 | other: около %{count} месяца 39 | about_x_years: 40 | one: около %{count} года 41 | other: около %{count} лет 42 | almost_x_years: 43 | one: почти %{count} год 44 | other: почти %{count} лет 45 | half_a_minute: меньше минуты 46 | less_than_x_minutes: 47 | one: меньше %{count} минуты 48 | other: меньше %{count} минуты 49 | less_than_x_seconds: 50 | one: меньше %{count} секунды 51 | other: меньше %{count} секунды 52 | over_x_years: 53 | one: больше %{count} года 54 | other: больше %{count} лет 55 | x_days: 56 | one: "%{count} день" 57 | other: "%{count} дня" 58 | x_minutes: 59 | one: "%{count} минуту" 60 | other: "%{count} минуты" 61 | x_months: 62 | one: "%{count} месяц" 63 | other: "%{count} месяца" 64 | x_seconds: 65 | one: "%{count} секунду" 66 | other: "%{count} секунды" 67 | number: 68 | precision: 69 | format: 70 | delimiter: '' 71 | format: 72 | delimiter: " " 73 | precision: 3 74 | separator: "," 75 | round_mode: default 76 | significant: false 77 | strip_insignificant_zeros: false 78 | percentage: 79 | format: 80 | delimiter: '' 81 | format: "%n%" 82 | human: 83 | format: 84 | delimiter: '' 85 | precision: 1 86 | significant: false 87 | strip_insignificant_zeros: false 88 | decimal_units: 89 | format: "%n %u" 90 | units: 91 | unit: '' 92 | billion: миллиард 93 | million: миллион 94 | quadrillion: квадриллион 95 | thousand: тысяча 96 | trillion: триллион 97 | storage_units: 98 | format: "%n %u" 99 | units: 100 | byte: 101 | one: байт 102 | other: байта 103 | gb: ГБ 104 | kb: КБ 105 | mb: МБ 106 | tb: ТБ 107 | pb: "" 108 | eb: "" 109 | 110 | currency: 111 | format: 112 | delimiter: " " 113 | format: "%n %u" 114 | precision: 2 115 | separator: "," 116 | significant: false 117 | strip_insignificant_zeros: false 118 | unit: руб. 119 | helpers: 120 | select: 121 | prompt: 'Выберите: ' 122 | submit: 123 | create: Создать %{model} 124 | submit: Сохранить %{model} 125 | update: Сохранить %{model} 126 | support: 127 | array: 128 | last_word_connector: " и " 129 | two_words_connector: " и " 130 | words_connector: ", " 131 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/us.rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | { 4 | us: { 5 | number: { 6 | nth: { 7 | ordinals: lambda do |_key, options| 8 | number = options[:number] 9 | case number 10 | when 1 then "st" 11 | when 2 then "nd" 12 | when 3 then "rd" 13 | when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 then "th" 14 | else 15 | num_modulo = number.to_i.abs % 100 16 | num_modulo %= 10 if num_modulo > 13 17 | case num_modulo 18 | when 1 then "st" 19 | when 2 then "nd" 20 | when 3 then "rd" 21 | else "th" 22 | end 23 | end 24 | end, 25 | 26 | ordinalized: lambda do |_key, options| 27 | number = options[:number] 28 | "#{number}#{ActiveSupport::Inflector.ordinal(number)}" 29 | end 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/dummy_app/config/locales/us.rails.yml: -------------------------------------------------------------------------------- 1 | us: 2 | # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() 3 | datetime: 4 | distance_in_words: 5 | half_a_minute: "half a minute" 6 | less_than_x_seconds: 7 | one: "less than 1 second" 8 | other: "less than %{count} seconds" 9 | x_seconds: 10 | one: "1 second" 11 | other: "%{count} seconds" 12 | less_than_x_minutes: 13 | one: "less than a minute" 14 | other: "less than %{count} minutes" 15 | x_minutes: 16 | one: "1 minute" 17 | other: "%{count} minutes" 18 | about_x_hours: 19 | one: "about 1 hour" 20 | other: "about %{count} hours" 21 | x_days: 22 | one: "1 day" 23 | other: "%{count} days" 24 | about_x_months: 25 | one: "about 1 month" 26 | other: "about %{count} months" 27 | x_months: 28 | one: "1 month" 29 | other: "%{count} months" 30 | about_x_years: 31 | one: "about 1 year" 32 | other: "about %{count} years" 33 | over_x_years: 34 | one: "over 1 year" 35 | other: "over %{count} years" 36 | almost_x_years: 37 | one: "almost 1 year" 38 | other: "almost %{count} years" 39 | prompts: 40 | year: "Year" 41 | month: "Month" 42 | day: "Day" 43 | hour: "Hour" 44 | minute: "Minute" 45 | second: "Seconds" 46 | 47 | helpers: 48 | select: 49 | # Default value for :prompt => true in FormOptionsHelper 50 | prompt: "Please select" 51 | 52 | # Default translation keys for submit and button FormHelper 53 | submit: 54 | create: 'Create %{model}' 55 | update: 'Update %{model}' 56 | submit: 'Save %{model}' 57 | date: 58 | formats: 59 | # Use the strftime parameters for formats. 60 | # When no format has been given, it uses default. 61 | # You can provide other formats here if you like! 62 | default: "%Y-%m-%d" 63 | short: "%b %d" 64 | long: "%B %d, %Y" 65 | 66 | day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] 67 | abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] 68 | 69 | # Don't forget the nil at the beginning; there's no such thing as a 0th month 70 | month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] 71 | abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] 72 | # Used in date_select and datetime_select. 73 | order: 74 | - year 75 | - month 76 | - day 77 | 78 | time: 79 | formats: 80 | default: "%a, %d %b %Y %H:%M:%S %z" 81 | short: "%d %b %H:%M" 82 | long: "%B %d, %Y %H:%M" 83 | am: "am" 84 | pm: "pm" 85 | 86 | # Used in array.to_sentence. 87 | support: 88 | array: 89 | words_connector: ", " 90 | two_words_connector: " and " 91 | last_word_connector: ", and " 92 | number: 93 | # Used in NumberHelper.number_to_delimited() 94 | # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' 95 | format: 96 | # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) 97 | separator: "." 98 | # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) 99 | delimiter: "," 100 | # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) 101 | precision: 3 102 | round_mode: default 103 | # If set to true, precision will mean the number of significant digits instead 104 | # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) 105 | significant: false 106 | # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) 107 | strip_insignificant_zeros: false 108 | 109 | # Used in NumberHelper.number_to_currency() 110 | currency: 111 | format: 112 | # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) 113 | format: "%u%n" 114 | unit: "$" 115 | # These five are to override number.format and are optional 116 | separator: "." 117 | delimiter: "," 118 | precision: 2 119 | significant: false 120 | strip_insignificant_zeros: false 121 | 122 | # Used in NumberHelper.number_to_percentage() 123 | percentage: 124 | format: 125 | # These five are to override number.format and are optional 126 | # separator: 127 | delimiter: "" 128 | # precision: 129 | # significant: false 130 | # strip_insignificant_zeros: false 131 | format: "%n%" 132 | 133 | # Used in NumberHelper.number_to_rounded() 134 | precision: 135 | format: 136 | # These five are to override number.format and are optional 137 | # separator: 138 | delimiter: "" 139 | # precision: 140 | # significant: false 141 | # strip_insignificant_zeros: false 142 | 143 | # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() 144 | human: 145 | format: 146 | # These five are to override number.format and are optional 147 | # separator: 148 | delimiter: "" 149 | precision: 3 150 | significant: true 151 | strip_insignificant_zeros: true 152 | # Used in number_to_human_size() 153 | storage_units: 154 | # Storage units output formatting. 155 | # %u is the storage unit, %n is the number (default: 2 MB) 156 | format: "%n %u" 157 | units: 158 | byte: 159 | one: "Byte" 160 | other: "Bytes" 161 | kb: "KB" 162 | mb: "MB" 163 | gb: "GB" 164 | tb: "TB" 165 | pb: "PB" 166 | eb: "EB" 167 | # Used in NumberHelper.number_to_human() 168 | decimal_units: 169 | format: "%n %u" 170 | # Decimal units output formatting 171 | # By default we will only quantify some of the exponents 172 | # but the commented ones might be defined or overridden 173 | # by the user. 174 | units: 175 | # femto: Quadrillionth 176 | # pico: Trillionth 177 | # nano: Billionth 178 | # micro: Millionth 179 | # mili: Thousandth 180 | # centi: Hundredth 181 | # deci: Tenth 182 | unit: "" 183 | # ten: 184 | # one: Ten 185 | # other: Tens 186 | # hundred: Hundred 187 | thousand: Thousand 188 | million: Million 189 | billion: Billion 190 | trillion: Trillion 191 | quadrillion: Quadrillion 192 | -------------------------------------------------------------------------------- /spec/localer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Localer do 4 | it "has a version number" do 5 | expect(Localer::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | require "localer" 5 | 6 | RSpec.configure do |config| 7 | # Disable RSpec exposing methods globally on `Module` and `main` 8 | config.disable_monkey_patching! 9 | 10 | config.expect_with :rspec do |c| 11 | c.syntax = :expect 12 | end 13 | end 14 | --------------------------------------------------------------------------------