localer 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +5 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +17 -1
- data/Appraisals +15 -0
- data/README.md +66 -6
- data/Rakefile +12 -3
- data/_config.yml +1 -0
- data/bin/localer +16 -8
- data/lib/localer.rb +13 -34
- data/lib/localer/config.rb +34 -11
- data/lib/localer/config/locale.rb +12 -0
- data/lib/localer/data.rb +39 -0
- data/lib/localer/data/checker.rb +18 -0
- data/lib/localer/data/missing_translations.rb +18 -0
- data/lib/localer/data/processor.rb +58 -0
- data/lib/localer/data/service.rb +18 -0
- data/lib/localer/ext/hash.rb +89 -0
- data/lib/localer/ext/string.rb +66 -0
- data/lib/localer/rails.rb +20 -0
- data/lib/localer/version.rb +1 -1
- data/localer.gemspec +4 -1
- metadata +56 -7
- data/lib/localer/checker.rb +0 -14
- data/lib/localer/missing_translations.rb +0 -14
- data/lib/localer/parser.rb +0 -39
- data/lib/localer/service.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 165de59d1c94a764cc7dbdec0c2327c2f96c75ef608dd86b4ea627ba2ff071cc
|
4
|
+
data.tar.gz: ba9fc37a955d84b70754bc03e894eb01093f9ca231bd0fce2622bf1fc4b19ead
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 382f19b3867430029b46eeb76f39d9c6366160f5a325170778c825be4160f067ee276afc0047577655c20415db03c11cfcd99b3342668a7c33a64e8f6039aa1d
|
7
|
+
data.tar.gz: eea447818e4ed693cf08e27b656d4a084a6b266dca554af277817532656fc1b304b830ee4dfb4aca97e450fed82f6c27430c89dba49d46b6ac8c3c2eb0dd5762
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -6,9 +6,13 @@ AllCops:
|
|
6
6
|
- 'spec/**/*.rb'
|
7
7
|
Exclude:
|
8
8
|
- 'bin/**/*'
|
9
|
+
- 'lib/localer/ext/*.rb'
|
10
|
+
- 'Appraisals'
|
9
11
|
- 'Gemfile'
|
10
12
|
- 'Rakefile'
|
11
13
|
- '*.gemspec'
|
14
|
+
- 'gemfiles/vendor/**/*'
|
15
|
+
- 'vendor/**/*'
|
12
16
|
DisplayCopNames: true
|
13
17
|
StyleGuideCopsOnly: false
|
14
18
|
TargetRubyVersion: 2.4
|
data/.travis.yml
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
sudo: false
|
2
|
+
dist: trusty
|
2
3
|
language: ruby
|
4
|
+
|
5
|
+
before_install:
|
6
|
+
- gem install bundler
|
3
7
|
rvm:
|
8
|
+
- 2.3.3
|
4
9
|
- 2.4.3
|
5
|
-
|
10
|
+
- 2.5.0
|
11
|
+
gemfile:
|
12
|
+
- gemfiles/rails50.gemfile
|
13
|
+
- gemfiles/rails51.gemfile
|
14
|
+
matrix:
|
15
|
+
include:
|
16
|
+
- rvm: 2.5.0
|
17
|
+
env: RUBOCOP=true
|
18
|
+
- rvm: 2.3.3
|
19
|
+
gemfile: gemfiles/rails40.gemfile
|
20
|
+
- rvm: 2.3.3
|
21
|
+
gemfile: gemfiles/rails41.gemfile
|
data/Appraisals
ADDED
data/README.md
CHANGED
@@ -1,11 +1,21 @@
|
|
1
|
-
|
1
|
+
[![Build Status](https://travis-ci.org/aderyabin/localer.svg?branch=master)](https://travis-ci.org/aderyabin/localer) [![Gem Version](https://badge.fury.io/rb/localer.svg)](https://rubygems.org/gems/localer)
|
2
2
|
|
3
|
-
|
3
|
+
# Localer
|
4
|
+
Localer is an automatic detecting missing I18n translations tool.
|
4
5
|
|
6
|
+
The aim is to preserve the integrity of translations.
|
7
|
+
Localer parses and merges keys for all locales in an application. At the next step, it searches missed translations for calculated keys.
|
5
8
|
|
6
|
-
<
|
7
|
-
<img src="https://
|
9
|
+
<p align="center">
|
10
|
+
<img align="center" height="500" src="https://gist.githubusercontent.com/aderyabin/cb0512cbcd6cb4c79a4d84a4831109a5/raw/localer2.png">
|
11
|
+
</p>
|
8
12
|
|
13
|
+
<p align="center">
|
14
|
+
<a href="https://evilmartians.com/?utm_source=localer">
|
15
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"
|
16
|
+
alt="Sponsored by Evil Martians" width="236" height="54">
|
17
|
+
</a>
|
18
|
+
</p>
|
9
19
|
|
10
20
|
## Installation
|
11
21
|
|
@@ -23,11 +33,61 @@ Or install it yourself as:
|
|
23
33
|
|
24
34
|
$ gem install localer
|
25
35
|
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
At the root directory of a Rails app, run:
|
39
|
+
```
|
40
|
+
localer check .
|
41
|
+
```
|
42
|
+
or for specific Rails path:
|
43
|
+
```
|
44
|
+
localer check /path/to/rails/application
|
45
|
+
```
|
46
|
+
## Support
|
47
|
+
|
48
|
+
Localer supports Ruby 2.3+, Rails. 4.1+
|
49
|
+
|
50
|
+
## Configuration
|
51
|
+
|
52
|
+
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.
|
53
|
+
|
54
|
+
#### Disable specific locale
|
55
|
+
|
56
|
+
By default, Localer enables all locales, but you can disable it:
|
57
|
+
|
58
|
+
```yml
|
59
|
+
Locale:
|
60
|
+
EN:
|
61
|
+
Enabled: false
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Exclude keys globally
|
65
|
+
By default, Localer enables all keys, but you can disable keys started with specified string or by regex:
|
66
|
+
|
67
|
+
```yml
|
68
|
+
Exclude:
|
69
|
+
- /population\z/
|
70
|
+
- .countries.france
|
71
|
+
```
|
72
|
+
|
73
|
+
#### Exclude keys for specific locale
|
74
|
+
```yml
|
75
|
+
Locale:
|
76
|
+
EN:
|
77
|
+
Exclude:
|
78
|
+
- /population\z/
|
79
|
+
- .countries.france
|
80
|
+
```
|
81
|
+
|
26
82
|
## Development
|
27
83
|
|
28
|
-
After checking out the repo, run `
|
84
|
+
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.
|
85
|
+
|
86
|
+
## Built With
|
29
87
|
|
30
|
-
|
88
|
+
* [Thor](https://github.com/erikhuda/thor) - Used for building command-line interfaces.
|
89
|
+
* [Appraisal](https://github.com/thoughtbot/appraisal) - Used for testing against different versions of dependencies
|
90
|
+
* [Cucumber](https://github.com/cucumber/cucumber) + [Aruba](https://github.com/cucumber/aruba) - Used for testing command-line commands
|
31
91
|
|
32
92
|
## Contributing
|
33
93
|
|
data/Rakefile
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require "rspec/core/rake_task"
|
3
2
|
require "rubocop/rake_task"
|
3
|
+
require 'cucumber/rake/task'
|
4
4
|
|
5
|
-
RSpec::Core::RakeTask.new(:spec)
|
6
5
|
RuboCop::RakeTask.new
|
7
6
|
|
8
|
-
|
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
|
data/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-leap-day
|
data/bin/localer
CHANGED
@@ -11,12 +11,16 @@ module Localer
|
|
11
11
|
say Localer::VERSION
|
12
12
|
end
|
13
13
|
|
14
|
-
desc "check", "Check
|
15
|
-
def check
|
16
|
-
|
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?
|
17
21
|
say "\xE2\x9C\x94 No missing translations found.", :green
|
18
22
|
else
|
19
|
-
missing_translations = Localer.missing_translations
|
23
|
+
missing_translations = Localer.data.missing_translations
|
20
24
|
say "\xE2\x9C\x96 Missing translations found (#{missing_translations.count}):", :red
|
21
25
|
missing_translations.each do |tr|
|
22
26
|
say "* #{tr}"
|
@@ -26,10 +30,14 @@ module Localer
|
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
data/lib/localer.rb
CHANGED
@@ -1,51 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry-initializer"
|
3
4
|
require_relative "localer/version"
|
4
|
-
require_relative "localer/
|
5
|
-
require_relative "localer/parser"
|
6
|
-
require_relative "localer/checker"
|
5
|
+
require_relative "localer/rails"
|
7
6
|
require_relative "localer/config"
|
8
|
-
require_relative "localer/
|
7
|
+
require_relative "localer/data"
|
9
8
|
|
10
9
|
module Localer # :nodoc:
|
11
|
-
|
12
|
-
|
13
|
-
@parcer ||= begin
|
14
|
-
require File.expand_path("config/environment", Dir.pwd)
|
15
|
-
I18n.backend.send(:init_translations)
|
16
|
-
Parser.call(I18n.backend.send(:translations))
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# returns array of available locales
|
21
|
-
def locales
|
22
|
-
parcer.locales
|
23
|
-
end
|
10
|
+
using Localer::Ext::Hash
|
11
|
+
# using Localer::Ext::String
|
24
12
|
|
13
|
+
class << self
|
25
14
|
def data
|
26
|
-
|
15
|
+
@data ||= load_data
|
27
16
|
end
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
Checker.call(locales, data)
|
32
|
-
end
|
33
|
-
|
34
|
-
# returns array of missing translations
|
35
|
-
def missing_translations
|
36
|
-
MissingTranslations.call(locales, data)
|
18
|
+
def config
|
19
|
+
@config ||= configure
|
37
20
|
end
|
38
21
|
|
39
|
-
def
|
40
|
-
@config
|
22
|
+
def configure(options = {})
|
23
|
+
@config = Config.load(options)
|
41
24
|
end
|
42
25
|
|
43
|
-
def
|
44
|
-
data
|
45
|
-
locales.each do |locale|
|
46
|
-
yield locale, key, value[locale]
|
47
|
-
end
|
48
|
-
end
|
26
|
+
def load_data(source = Localer::Rails.translations)
|
27
|
+
@data = Data.new(source)
|
49
28
|
end
|
50
29
|
end
|
51
30
|
end
|
data/lib/localer/config.rb
CHANGED
@@ -1,22 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
+
require_relative '../localer/ext/hash'
|
5
|
+
require_relative 'config/locale'
|
6
|
+
|
7
|
+
module Localer # :nodoc:
|
8
|
+
using Localer::Ext::Hash
|
4
9
|
|
5
|
-
module Localer
|
6
10
|
# Loads and parse Localer config file `.localer.yml`
|
7
|
-
class Config
|
11
|
+
class Config
|
12
|
+
extend Dry::Initializer
|
13
|
+
|
14
|
+
APP_PATH = Dir.pwd
|
15
|
+
CONFIG_FILENAME = ".localer.yml"
|
16
|
+
|
8
17
|
option :exclude, default: -> { [] }
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
15
28
|
|
16
|
-
|
29
|
+
def file_config(filename, path)
|
30
|
+
filename = File.expand_path(filename, path)
|
31
|
+
return {} unless File.exist?(filename)
|
32
|
+
YAML
|
33
|
+
.load_file(filename)
|
34
|
+
.deep_downcase_keys
|
35
|
+
.deep_symbolize_keys
|
36
|
+
end
|
17
37
|
|
18
|
-
|
19
|
-
|
38
|
+
def parse_locales(hash)
|
39
|
+
hash.each_with_object(Hash.new(Locale.new)) do |(l, v), h|
|
40
|
+
h[l] = Locale.new(v)
|
41
|
+
end
|
42
|
+
end
|
20
43
|
end
|
21
44
|
end
|
22
45
|
end
|
data/lib/localer/data.rb
ADDED
@@ -0,0 +1,39 @@
|
|
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
|
@@ -0,0 +1,18 @@
|
|
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
|
@@ -0,0 +1,18 @@
|
|
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
|
@@ -0,0 +1,58 @@
|
|
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].enabled
|
22
|
+
@locales.push locale
|
23
|
+
prepare(locale, translation)
|
24
|
+
end
|
25
|
+
[@locales, @data]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def prepare(locale, translation, prefix = "")
|
31
|
+
if translation.is_a?(Hash)
|
32
|
+
translation.each do |(key, value)|
|
33
|
+
full_key = prefix + ".#{key}"
|
34
|
+
next if exclude?(full_key, locale)
|
35
|
+
prepare(locale, value, full_key)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# @data[prefix] ||= {}
|
39
|
+
@data[prefix][locale] = translation
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def exclude?(key, locale)
|
44
|
+
(config.exclude + config.locale[locale].exclude).any? do |pattern|
|
45
|
+
match?(key, pattern)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def match?(key, pattern)
|
50
|
+
if (regex = pattern.to_regexp)
|
51
|
+
key =~ regex
|
52
|
+
else
|
53
|
+
key.start_with?(pattern)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
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
|
@@ -0,0 +1,89 @@
|
|
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
|
@@ -0,0 +1,66 @@
|
|
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
|
@@ -0,0 +1,20 @@
|
|
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
|
+
I18n.backend.send(:init_translations)
|
16
|
+
I18n.backend.send(:translations)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/localer/version.rb
CHANGED
data/localer.gemspec
CHANGED
@@ -14,16 +14,19 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = "https://github.com/aderyabin/localer"
|
15
15
|
spec.license = "MIT"
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
-
f.match(%r{^(test|spec|features)/})
|
17
|
+
f.match(%r{^(test|spec|features|gemfiles)/})
|
18
18
|
end
|
19
19
|
spec.bindir = "bin"
|
20
20
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_development_dependency "appraisal"
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.16"
|
24
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
27
|
spec.add_development_dependency "rubocop", "~> 0.50"
|
28
|
+
spec.add_development_dependency "cucumber"
|
29
|
+
spec.add_development_dependency "aruba"
|
27
30
|
|
28
31
|
spec.add_dependency "thor", "~> 0.20.0"
|
29
32
|
spec.add_dependency "dry-initializer", "~> 2.3.0"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: localer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Deryabin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: appraisal
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,34 @@ dependencies:
|
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0.50'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: cucumber
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aruba
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
112
|
name: thor
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,18 +148,25 @@ files:
|
|
106
148
|
- ".rspec"
|
107
149
|
- ".rubocop.yml"
|
108
150
|
- ".travis.yml"
|
151
|
+
- Appraisals
|
109
152
|
- CODE_OF_CONDUCT.md
|
110
153
|
- Gemfile
|
111
154
|
- LICENSE.txt
|
112
155
|
- README.md
|
113
156
|
- Rakefile
|
157
|
+
- _config.yml
|
114
158
|
- bin/localer
|
115
159
|
- lib/localer.rb
|
116
|
-
- lib/localer/checker.rb
|
117
160
|
- lib/localer/config.rb
|
118
|
-
- lib/localer/
|
119
|
-
- lib/localer/
|
120
|
-
- lib/localer/
|
161
|
+
- lib/localer/config/locale.rb
|
162
|
+
- lib/localer/data.rb
|
163
|
+
- lib/localer/data/checker.rb
|
164
|
+
- lib/localer/data/missing_translations.rb
|
165
|
+
- lib/localer/data/processor.rb
|
166
|
+
- lib/localer/data/service.rb
|
167
|
+
- lib/localer/ext/hash.rb
|
168
|
+
- lib/localer/ext/string.rb
|
169
|
+
- lib/localer/rails.rb
|
121
170
|
- lib/localer/version.rb
|
122
171
|
- localer.gemspec
|
123
172
|
homepage: https://github.com/aderyabin/localer
|
@@ -140,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
189
|
version: '0'
|
141
190
|
requirements: []
|
142
191
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.
|
192
|
+
rubygems_version: 2.7.4
|
144
193
|
signing_key:
|
145
194
|
specification_version: 4
|
146
195
|
summary: Automatic detecting missing I18n translations tool.
|
data/lib/localer/checker.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Localer
|
4
|
-
# Check missing translations
|
5
|
-
# Returns true if no missing translations found, otherwise false
|
6
|
-
class Checker < Service
|
7
|
-
def call
|
8
|
-
Localer.each_data do |_locale, _key, value|
|
9
|
-
return false if value.nil?
|
10
|
-
end
|
11
|
-
true
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Localer
|
4
|
-
# A service that returns array of missing translations
|
5
|
-
class MissingTranslations < Service
|
6
|
-
def call
|
7
|
-
missing = []
|
8
|
-
Localer.each_data do |locale, key, value|
|
9
|
-
missing.push("#{locale}#{key}") if value.nil?
|
10
|
-
end
|
11
|
-
missing
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/lib/localer/parser.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Localer
|
4
|
-
# Parse translations into hash:
|
5
|
-
# key: translation key
|
6
|
-
# value: hash of locale values
|
7
|
-
class Parser < Service
|
8
|
-
param :translations
|
9
|
-
param :config, default: -> { Localer.config }
|
10
|
-
|
11
|
-
attr_reader :data, :locales
|
12
|
-
|
13
|
-
def call
|
14
|
-
@data = {}
|
15
|
-
@locales = []
|
16
|
-
@translations.each do |(locale, translation)|
|
17
|
-
@locales.push locale
|
18
|
-
compare(locale, translation)
|
19
|
-
end
|
20
|
-
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def compare(locale, translation, prefix = "")
|
27
|
-
if translation.is_a?(Hash)
|
28
|
-
translation.each do |(key, value)|
|
29
|
-
full_key = prefix + ".#{key}"
|
30
|
-
next if full_key.start_with?(*config.exclude)
|
31
|
-
compare(locale, value, full_key)
|
32
|
-
end
|
33
|
-
else
|
34
|
-
@data[prefix] ||= {}
|
35
|
-
@data[prefix][locale] = translation
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/lib/localer/service.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'dry-initializer'
|
4
|
-
module Localer
|
5
|
-
# Core service object
|
6
|
-
class Service
|
7
|
-
extend Dry::Initializer # use `param` and `option` for dependencies
|
8
|
-
|
9
|
-
class << self
|
10
|
-
# Instantiates and calls the service at once
|
11
|
-
def call(*args, &block)
|
12
|
-
new(*args).call(&block)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Accepts both symbolized and stringified attributes
|
16
|
-
def new(*args)
|
17
|
-
args << args.pop.symbolize_keys if args.last.is_a?(Hash)
|
18
|
-
super(*args)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|