i18n-hygiene 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +109 -0
- data/lib/i18n/hygiene.rb +1 -4
- data/lib/i18n/hygiene/checks/base.rb +26 -0
- data/lib/i18n/hygiene/checks/html_entity.rb +40 -0
- data/lib/i18n/hygiene/checks/key_usage.rb +42 -0
- data/lib/i18n/hygiene/checks/missing_interpolation_variable.rb +37 -0
- data/lib/i18n/hygiene/checks/script_tag.rb +34 -0
- data/lib/i18n/hygiene/checks/unexpected_return_symbol.rb +34 -0
- data/lib/i18n/hygiene/config.rb +52 -0
- data/lib/i18n/hygiene/error_message_builder.rb +82 -0
- data/lib/i18n/hygiene/key_usage_checker.rb +38 -27
- data/lib/i18n/hygiene/keys_with_matched_value.rb +6 -9
- data/lib/i18n/hygiene/locale_translations.rb +9 -31
- data/lib/i18n/hygiene/rake_task.rb +72 -0
- data/lib/i18n/hygiene/reporter.rb +43 -0
- data/lib/i18n/hygiene/result.rb +23 -0
- data/lib/i18n/hygiene/variable_checker.rb +8 -34
- data/lib/i18n/hygiene/wrapper.rb +25 -4
- metadata +76 -13
- data/lib/i18n/hygiene/keys_with_entities.rb +0 -29
- data/lib/i18n/hygiene/keys_with_return_symbol.rb +0 -21
- data/lib/i18n/hygiene/keys_with_script_tags.rb +0 -21
- data/lib/i18n/hygiene/railtie.rb +0 -9
- data/lib/tasks/i18n_hygiene.rake +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a0ed252e0cc76261202f3e8c8224a9d2ba89d82
|
4
|
+
data.tar.gz: 70f696d78272fc56810a9243d8e12471865ec54f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8226fec6814080e9240c23e6ad77ced8a38e7e301385f40c9f6cc43f5b7c707cfc7447f0fded226b67957c49f0c03ba42700b1e9eb75e4624c26c214cfd24f2
|
7
|
+
data.tar.gz: a49070ce84895f31cb18ce08936643aa7fcfaea784a79b578654181141e910fc7a3de3d90cc3a3098311bc2d4fda29871dbb07db092aea08b7a3fc85b5b35f8c
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# i18n-hygiene [![Build Status](https://travis-ci.org/conversation/i18n-hygiene.svg?branch=master)](https://travis-ci.org/conversation/i18n-hygiene)
|
2
|
+
|
3
|
+
Provides a configurable rake task to help maintain your translations.
|
4
|
+
|
5
|
+
Over the lifetime of a project there tends to be a lot of churn in the translation files. Contexts and meanings will change, features will be added and removed. You'll find that soon enough, translations fall out of use, mistakes by developers or translators will creep in. The longer this goes on, the harder it is to keep them neat and tidy.
|
6
|
+
|
7
|
+
This tool is intended to be used as part of your continuous integration pipeline to keep your translations healthy and prevent issues from ever making it to production.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
Include the gem in your gemfile and bundle:
|
12
|
+
|
13
|
+
`gem 'i18n-hygiene'`
|
14
|
+
|
15
|
+
## Integrating with rake
|
16
|
+
|
17
|
+
Create a rake task with the desired configuration. For example, this will create a rake task called `i18n:hygiene`:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
namespace :i18n do
|
21
|
+
I18n::Hygiene::RakeTask.new do |config|
|
22
|
+
config.directories = ["app", "lib"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
```
|
27
|
+
|
28
|
+
You can then run the rake task with:
|
29
|
+
```
|
30
|
+
bundle exec rake i18n:hygiene
|
31
|
+
```
|
32
|
+
|
33
|
+
You could also create separate rake tasks with different names and configurations, this may be useful if you are in the middle of rolling out a new locale:
|
34
|
+
```ruby
|
35
|
+
namespace :i18n do
|
36
|
+
I18n::Hygiene::RakeTask.new(:hygiene_live) do |config|
|
37
|
+
config.locales = [:fr]
|
38
|
+
end
|
39
|
+
|
40
|
+
I18n::Hygiene::RakeTask.new(:hygiene_wip) do |config|
|
41
|
+
config.locales = [:es]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Which could be run like:
|
47
|
+
|
48
|
+
```
|
49
|
+
bundle exec rake i18n:hygiene_live
|
50
|
+
bundle exec rake i18n:hygiene_wip
|
51
|
+
```
|
52
|
+
|
53
|
+
## Configuration
|
54
|
+
|
55
|
+
| Configuration | Default | Description |
|
56
|
+
|---|---|---|
|
57
|
+
| `concurrency` | Number of CPU cores | How many threads to use for key usage check |
|
58
|
+
| `directories` | All | Directories to search for key usage |
|
59
|
+
| `exclude_files` | None | Excludes files from key usage check |
|
60
|
+
| `exclude_keys` | None | Exclude individual keys |
|
61
|
+
| `exclude_scopes` | None | Exclude groups of keys |
|
62
|
+
| `file_extensions` | `rb, erb, coffee, js, jsx` | Only look in files with these extensions for key usage |
|
63
|
+
| `primary_locale` | `I18n.default_locale` | Translations from other locales are checked against this |
|
64
|
+
| `locales` | `I18n.available_locales` | Translations from these are checked against primary |
|
65
|
+
|
66
|
+
Example using all configuration options:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
I18n::Hygiene::RakeTask.new do |config|
|
70
|
+
config.concurrency = 16
|
71
|
+
config.directories = ["app", "lib"]
|
72
|
+
config.exclude_files = ["README.md"]
|
73
|
+
config.file_extensions = ["rb", "jsx"]
|
74
|
+
config.primary_locale = :en
|
75
|
+
config.locales = [:ja, :kr]
|
76
|
+
config.exclude_keys = [
|
77
|
+
"my.dynamically.used.key",
|
78
|
+
"another.dynamically.used.key"
|
79
|
+
]
|
80
|
+
config.exclude_scopes = [
|
81
|
+
"activerecord",
|
82
|
+
"countries"
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
#### Without Rails
|
89
|
+
|
90
|
+
Using this gem without Rails is possible, but you'll need to load the translations manually first.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
namespace :i18n do
|
94
|
+
require 'i18n'
|
95
|
+
require 'i18n/hygiene'
|
96
|
+
|
97
|
+
I18n.load_path = Dir["locales/*.yml"]
|
98
|
+
I18n.backend.load_translations
|
99
|
+
|
100
|
+
I18n::Hygiene::RakeTask.new(:hygiene_live) do |config|
|
101
|
+
config.directories = ["src"]
|
102
|
+
config.locales = [:fr]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## License
|
108
|
+
|
109
|
+
MIT
|
data/lib/i18n/hygiene.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
require 'i18n/hygiene/railtie' if defined?(Rails)
|
2
1
|
require 'i18n/hygiene/key_usage_checker'
|
3
|
-
require 'i18n/hygiene/keys_with_entities'
|
4
2
|
require 'i18n/hygiene/keys_with_matched_value'
|
5
|
-
require 'i18n/hygiene/keys_with_return_symbol'
|
6
|
-
require 'i18n/hygiene/keys_with_script_tags'
|
7
3
|
require 'i18n/hygiene/locale_translations'
|
8
4
|
require 'i18n/hygiene/variable_checker'
|
9
5
|
require 'i18n/hygiene/wrapper'
|
6
|
+
require 'i18n/hygiene/rake_task'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module I18n
|
2
|
+
module Hygiene
|
3
|
+
module Checks
|
4
|
+
class Base
|
5
|
+
def initialize(config)
|
6
|
+
raise "Must pass an instance of Config" unless config.is_a?(I18n::Hygiene::Config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
raise "#run must be implemented by subclass"
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def config
|
17
|
+
@config
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_locales
|
21
|
+
config.all_locales
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'i18n/hygiene/checks/base'
|
2
|
+
require 'i18n/hygiene/keys_with_matched_value'
|
3
|
+
require 'i18n/hygiene/result'
|
4
|
+
require 'i18n/hygiene/wrapper'
|
5
|
+
require 'i18n/hygiene/error_message_builder'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Hygiene
|
9
|
+
module Checks
|
10
|
+
##
|
11
|
+
# Looks for unexpected HTML entities (`&`, `!`) in translations.
|
12
|
+
class HtmlEntity < Base
|
13
|
+
ENTITY_REGEX = /&\w+;/
|
14
|
+
|
15
|
+
def run
|
16
|
+
wrapper = I18n::Hygiene::Wrapper.new(locales: all_locales, exclude_scopes: config.exclude_scopes)
|
17
|
+
|
18
|
+
keys_with_entities = I18n::Hygiene::KeysWithMatchedValue.new(ENTITY_REGEX, wrapper, reject_keys: reject_keys)
|
19
|
+
|
20
|
+
keys_with_entities.each do |locale, key|
|
21
|
+
message = ErrorMessageBuilder.new
|
22
|
+
.title("Unexpected HTML entity")
|
23
|
+
.locale(locale)
|
24
|
+
.key(key)
|
25
|
+
.translation(wrapper.value(locale, key))
|
26
|
+
.create
|
27
|
+
|
28
|
+
yield Result.new(:failure, message: message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def reject_keys
|
35
|
+
Proc.new { |key| key.end_with?("_html") || key.end_with?("_markdown") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
require 'i18n/hygiene/wrapper'
|
3
|
+
require 'i18n/hygiene/checks/base'
|
4
|
+
require 'i18n/hygiene/key_usage_checker'
|
5
|
+
require 'i18n/hygiene/result'
|
6
|
+
require 'i18n/hygiene/error_message_builder'
|
7
|
+
|
8
|
+
module I18n
|
9
|
+
module Hygiene
|
10
|
+
module Checks
|
11
|
+
##
|
12
|
+
# Ensures that existing translations are actually used.
|
13
|
+
class KeyUsage < Base
|
14
|
+
def run
|
15
|
+
key_usage_checker = I18n::Hygiene::KeyUsageChecker.new(
|
16
|
+
directories: config.directories,
|
17
|
+
exclude_files: config.exclude_files,
|
18
|
+
file_extensions: config.file_extensions
|
19
|
+
)
|
20
|
+
|
21
|
+
wrapper = I18n::Hygiene::Wrapper.new(
|
22
|
+
exclude_keys: config.exclude_keys,
|
23
|
+
exclude_scopes: config.exclude_scopes
|
24
|
+
)
|
25
|
+
|
26
|
+
Parallel.each(wrapper.keys_to_check(config.primary_locale), in_threads: config.concurrency) do |key|
|
27
|
+
if key_usage_checker.used?(key)
|
28
|
+
yield Result.new(:pass, message: ".")
|
29
|
+
else
|
30
|
+
message = ErrorMessageBuilder.new
|
31
|
+
.title("Unused translation")
|
32
|
+
.key(key)
|
33
|
+
.create
|
34
|
+
|
35
|
+
yield Result.new(:failure, message: message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'i18n/hygiene/wrapper'
|
2
|
+
require 'i18n/hygiene/checks/base'
|
3
|
+
require 'i18n/hygiene/variable_checker'
|
4
|
+
require 'i18n/hygiene/result'
|
5
|
+
require 'i18n/hygiene/error_message_builder'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Hygiene
|
9
|
+
module Checks
|
10
|
+
##
|
11
|
+
# Looks for translations which are missing interpolation variables.
|
12
|
+
class MissingInterpolationVariable < Base
|
13
|
+
def run
|
14
|
+
wrapper = I18n::Hygiene::Wrapper.new(exclude_scopes: config.exclude_scopes)
|
15
|
+
|
16
|
+
wrapper.keys_to_check(config.primary_locale).select do |key|
|
17
|
+
checker = I18n::Hygiene::VariableChecker.new(key, wrapper, config.primary_locale, config.locales)
|
18
|
+
|
19
|
+
checker.mismatched_variables do |locale, key, missing_variables|
|
20
|
+
if missing_variables.any?
|
21
|
+
message = ErrorMessageBuilder.new
|
22
|
+
.title("Missing interpolation variable(s)")
|
23
|
+
.locale(locale)
|
24
|
+
.key(key)
|
25
|
+
.translation(wrapper.value(locale, key))
|
26
|
+
.expected(missing_variables.join(", "))
|
27
|
+
.create
|
28
|
+
|
29
|
+
yield Result.new(:failure, message: message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'i18n/hygiene/checks/base'
|
2
|
+
require 'i18n/hygiene/keys_with_matched_value'
|
3
|
+
require 'i18n/hygiene/result'
|
4
|
+
require 'i18n/hygiene/wrapper'
|
5
|
+
require 'i18n/hygiene/error_message_builder'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Hygiene
|
9
|
+
module Checks
|
10
|
+
##
|
11
|
+
# Looks for unexpected script tags in translations.
|
12
|
+
class ScriptTag < Base
|
13
|
+
SCRIPT_TAG_REGEX = /<script.*/
|
14
|
+
|
15
|
+
def run
|
16
|
+
wrapper = I18n::Hygiene::Wrapper.new(locales: all_locales, exclude_scopes: config.exclude_scopes)
|
17
|
+
|
18
|
+
keys_with_script_tags = I18n::Hygiene::KeysWithMatchedValue.new(SCRIPT_TAG_REGEX, wrapper)
|
19
|
+
|
20
|
+
keys_with_script_tags.each do |locale, key|
|
21
|
+
message = ErrorMessageBuilder.new
|
22
|
+
.title("Unexpected script tag")
|
23
|
+
.locale(locale)
|
24
|
+
.key(key)
|
25
|
+
.translation(wrapper.value(locale, key))
|
26
|
+
.create
|
27
|
+
|
28
|
+
yield Result.new(:failure, message: message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'i18n/hygiene/checks/base'
|
2
|
+
require 'i18n/hygiene/keys_with_matched_value'
|
3
|
+
require 'i18n/hygiene/result'
|
4
|
+
require 'i18n/hygiene/error_message_builder'
|
5
|
+
|
6
|
+
module I18n
|
7
|
+
module Hygiene
|
8
|
+
module Checks
|
9
|
+
##
|
10
|
+
# Looks for unexpected return symbols (U+23CE) in translations.
|
11
|
+
#
|
12
|
+
# This check is fairly specific to PhraseApp, where U+23CE has special meaning.
|
13
|
+
class UnexpectedReturnSymbol < Base
|
14
|
+
RETURN_SYMBOL_REGEX = /\u23ce/
|
15
|
+
|
16
|
+
def run
|
17
|
+
wrapper = I18n::Hygiene::Wrapper.new(locales: all_locales, exclude_scopes: config.exclude_scopes)
|
18
|
+
keys_with_return_symbols = I18n::Hygiene::KeysWithMatchedValue.new(RETURN_SYMBOL_REGEX, wrapper)
|
19
|
+
|
20
|
+
keys_with_return_symbols.each do |locale, key|
|
21
|
+
message = ErrorMessageBuilder.new
|
22
|
+
.title("Unexpected return symbol (U+23CE)")
|
23
|
+
.locale(locale)
|
24
|
+
.key(key)
|
25
|
+
.translation(wrapper.value(locale, key))
|
26
|
+
.create
|
27
|
+
|
28
|
+
yield Result.new(:failure, message: message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Hygiene
|
5
|
+
class Config
|
6
|
+
attr_writer :exclude_files
|
7
|
+
attr_writer :directories
|
8
|
+
attr_writer :file_extensions
|
9
|
+
attr_writer :primary_locale
|
10
|
+
attr_writer :locales
|
11
|
+
attr_writer :exclude_keys
|
12
|
+
attr_writer :concurrency
|
13
|
+
attr_writer :exclude_scopes
|
14
|
+
|
15
|
+
def exclude_files
|
16
|
+
@exclude_files ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def directories
|
20
|
+
@directories ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_extensions
|
24
|
+
@file_extensions ||= ["rb", "erb", "coffee", "js", "jsx"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def primary_locale
|
28
|
+
@primary_locale ||= ::I18n.default_locale
|
29
|
+
end
|
30
|
+
|
31
|
+
def locales
|
32
|
+
@locales ||= ::I18n.available_locales
|
33
|
+
end
|
34
|
+
|
35
|
+
def all_locales
|
36
|
+
[primary_locale] + locales
|
37
|
+
end
|
38
|
+
|
39
|
+
def exclude_keys
|
40
|
+
@exclude_keys ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def concurrency
|
44
|
+
@concurrency || Parallel.processor_count
|
45
|
+
end
|
46
|
+
|
47
|
+
def exclude_scopes
|
48
|
+
@exclude_scopes ||= []
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Hygiene
|
5
|
+
class ErrorMessageBuilder
|
6
|
+
LEFT_PAD = " " * 2
|
7
|
+
TRUNCATE_LIMIT = 48
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@title = "Unspecified Error"
|
11
|
+
@key = "unknown_key"
|
12
|
+
@locale = nil
|
13
|
+
@translation = nil
|
14
|
+
@location = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def title(title)
|
18
|
+
@title = title
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def locale(locale)
|
23
|
+
@locale = locale
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def key(key)
|
28
|
+
@key = key
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def expected(expected)
|
33
|
+
@expected = expected
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def translation(translation)
|
38
|
+
@translation = translation
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def create
|
43
|
+
s = StringIO.new
|
44
|
+
s << "\n"
|
45
|
+
s << Rainbow("i18n-hygiene/#{@title}:").red
|
46
|
+
s << "\n"
|
47
|
+
s << LEFT_PAD
|
48
|
+
|
49
|
+
if @locale
|
50
|
+
s << "#{@locale}."
|
51
|
+
end
|
52
|
+
|
53
|
+
s << @key
|
54
|
+
|
55
|
+
if @translation
|
56
|
+
s << ": "
|
57
|
+
s << Rainbow("\"#{truncated_translation}\"").yellow
|
58
|
+
end
|
59
|
+
|
60
|
+
if @expected
|
61
|
+
s << "\n"
|
62
|
+
s << LEFT_PAD * 2
|
63
|
+
s << "Expected: "
|
64
|
+
s << Rainbow(@expected).color(:orange)
|
65
|
+
end
|
66
|
+
|
67
|
+
s << "\n"
|
68
|
+
s.string
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def truncated_translation
|
74
|
+
if @translation.length > TRUNCATE_LIMIT
|
75
|
+
@translation[0..TRUNCATE_LIMIT] + "..."
|
76
|
+
else
|
77
|
+
@translation
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,50 +1,61 @@
|
|
1
1
|
module I18n
|
2
2
|
module Hygiene
|
3
|
-
# Checks the usage of i18n keys in the
|
4
|
-
#
|
5
|
-
# TODO: This class needs some work, I had to strip out a bunch of functionality around
|
6
|
-
# the dynamically used keys because it was all specific to TC.
|
7
|
-
#
|
8
|
-
# We need to add a way to configure the hygiene checks to "whitelist" certain dynamic
|
9
|
-
# keys and so on.
|
10
|
-
#
|
11
|
-
# The other issue is that we hard code the folders we scan for fully qualified keys
|
12
|
-
# which should also be configurable.
|
3
|
+
# Checks the usage of i18n keys in the codebase.
|
13
4
|
class KeyUsageChecker
|
14
5
|
|
15
|
-
|
6
|
+
def initialize(directories:, exclude_files: [], file_extensions: [])
|
7
|
+
@directories = directories
|
8
|
+
@exclude_files = exclude_files
|
9
|
+
@file_extensions = file_extensions
|
16
10
|
|
17
|
-
|
18
|
-
@key = key
|
11
|
+
raise "Must have git installed!" unless system("which git > /dev/null")
|
19
12
|
end
|
20
13
|
|
21
|
-
def
|
22
|
-
fully_qualified_key_used?
|
14
|
+
def used?(key)
|
15
|
+
i18n_config_key?(key) || fully_qualified_key_used?(key)
|
23
16
|
end
|
24
17
|
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
private
|
19
|
+
|
20
|
+
def fully_qualified_key_used?(key)
|
21
|
+
if pluralized_key_used?(key)
|
22
|
+
fully_qualified_key_used?(without_last_part(key))
|
28
23
|
else
|
29
|
-
%x
|
24
|
+
%x<git grep #{key} #{git_grep_options} | wc -l>.strip.to_i > 0
|
30
25
|
end
|
31
26
|
end
|
32
27
|
|
33
|
-
|
28
|
+
def git_grep_options
|
29
|
+
[git_grep_include, git_grep_exclude].reject(&:empty?).join(" ")
|
30
|
+
end
|
34
31
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
def git_grep_include
|
33
|
+
@directories.map { |dir|
|
34
|
+
if @file_extensions.empty?
|
35
|
+
dir
|
36
|
+
else
|
37
|
+
@file_extensions.map { |ext|
|
38
|
+
"'#{dir}/*.#{ext}'"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
}.flatten.join(" ")
|
42
|
+
end
|
43
|
+
|
44
|
+
def git_grep_exclude
|
45
|
+
@exclude_files.map { |file|
|
46
|
+
"':(exclude)*#{file}'"
|
47
|
+
}.join(" ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def i18n_config_key?(key)
|
51
|
+
key.start_with?("i18n.")
|
41
52
|
end
|
42
53
|
|
43
54
|
def pluralized_key_used?(key)
|
44
55
|
[ "zero", "one", "other" ].include?(key.split(".").last)
|
45
56
|
end
|
46
57
|
|
47
|
-
def without_last_part
|
58
|
+
def without_last_part(key)
|
48
59
|
key.split(".")[0..-2].join(".")
|
49
60
|
end
|
50
61
|
|
@@ -6,23 +6,20 @@ module I18n
|
|
6
6
|
|
7
7
|
def initialize(regex, i18n_wrapper = nil, reject_keys: nil)
|
8
8
|
@regex = regex
|
9
|
-
@i18n = i18n_wrapper || I18n::Hygiene::Wrapper.new
|
9
|
+
@i18n = i18n_wrapper || I18n::Hygiene::Wrapper.new(exclude_keys: [])
|
10
10
|
@reject_keys = reject_keys
|
11
|
-
@matching_keys = load_matching_keys
|
12
11
|
end
|
13
12
|
|
14
13
|
def each(&block)
|
15
|
-
|
14
|
+
locales.each do |locale|
|
15
|
+
matching_keys(locale).each do |key|
|
16
|
+
block.call(locale, key)
|
17
|
+
end
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
19
22
|
|
20
|
-
def load_matching_keys
|
21
|
-
locales.inject([]) do |results, locale|
|
22
|
-
results + matching_keys(locale).map { |key| "#{locale}: #{key}" }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
23
|
def matching_keys(locale)
|
27
24
|
keys_to_check(locale).select { |key| i18n.value(locale, key).to_s.match(regex) }
|
28
25
|
end
|
@@ -10,52 +10,30 @@ module I18n
|
|
10
10
|
# These are i18n keys provided by Rails. We cannot exclude them at the :helpers
|
11
11
|
# scope level because we do have some TC i18n keys scoped within :helpers.
|
12
12
|
|
13
|
-
|
14
|
-
KEYS_TO_SKIP = [
|
15
|
-
"helpers.select.prompt",
|
16
|
-
"helpers.submit.create",
|
17
|
-
"helpers.submit.submit",
|
18
|
-
"helpers.submit.update"
|
19
|
-
]
|
20
|
-
|
21
|
-
def initialize(translations)
|
13
|
+
def initialize(translations:, exclude_keys:, exclude_scopes:)
|
22
14
|
@translations = translations
|
15
|
+
@exclude_keys = exclude_keys || []
|
16
|
+
@exclude_scopes = exclude_scopes || []
|
23
17
|
end
|
24
18
|
|
25
19
|
def keys_to_check
|
26
20
|
fully_qualified_keys(translations_to_check).reject { |key|
|
27
|
-
|
21
|
+
exclude_keys.include?(key) || EXAMPLE_KEY == key
|
28
22
|
}.sort
|
29
23
|
end
|
30
24
|
|
31
25
|
private
|
32
26
|
|
33
27
|
def translations_to_check
|
34
|
-
@translations.reject { |k, _v|
|
35
|
-
end
|
36
|
-
|
37
|
-
def non_tc_scopes
|
38
|
-
scopes_from_rails + scopes_from_devise + scopes_from_kaminari + scopes_from_i18n_country_select + scopes_from_faker
|
39
|
-
end
|
40
|
-
|
41
|
-
def scopes_from_rails
|
42
|
-
[ :activerecord, :date, :datetime, :errors, :number, :support, :time ]
|
43
|
-
end
|
44
|
-
|
45
|
-
def scopes_from_devise
|
46
|
-
[ :devise ]
|
47
|
-
end
|
48
|
-
|
49
|
-
def scopes_from_kaminari
|
50
|
-
[ :views ]
|
28
|
+
@translations.reject { |k, _v| exclude_scopes.include? k }
|
51
29
|
end
|
52
30
|
|
53
|
-
def
|
54
|
-
|
31
|
+
def exclude_keys
|
32
|
+
@exclude_keys
|
55
33
|
end
|
56
34
|
|
57
|
-
def
|
58
|
-
|
35
|
+
def exclude_scopes
|
36
|
+
@exclude_scopes
|
59
37
|
end
|
60
38
|
|
61
39
|
def fully_qualified_keys(hash)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'i18n/hygiene/config'
|
4
|
+
require 'i18n/hygiene/reporter'
|
5
|
+
require 'i18n/hygiene/checks/html_entity'
|
6
|
+
require 'i18n/hygiene/checks/key_usage'
|
7
|
+
require 'i18n/hygiene/checks/missing_interpolation_variable'
|
8
|
+
require 'i18n/hygiene/checks/script_tag'
|
9
|
+
require 'i18n/hygiene/checks/unexpected_return_symbol'
|
10
|
+
|
11
|
+
module I18n
|
12
|
+
module Hygiene
|
13
|
+
class RakeTask < ::Rake::TaskLib
|
14
|
+
CHECKS = [
|
15
|
+
I18n::Hygiene::Checks::KeyUsage,
|
16
|
+
I18n::Hygiene::Checks::MissingInterpolationVariable,
|
17
|
+
I18n::Hygiene::Checks::HtmlEntity,
|
18
|
+
I18n::Hygiene::Checks::ScriptTag,
|
19
|
+
I18n::Hygiene::Checks::UnexpectedReturnSymbol,
|
20
|
+
]
|
21
|
+
|
22
|
+
def initialize(task_name = :hygiene, &block)
|
23
|
+
config = Config.new
|
24
|
+
|
25
|
+
if block
|
26
|
+
block.call(config)
|
27
|
+
|
28
|
+
# We always want to exclude the file that is configuring this rake task
|
29
|
+
config.exclude_files = config.exclude_files + [relative_path_for(block.source_location)]
|
30
|
+
end
|
31
|
+
|
32
|
+
unless ::Rake.application.last_description
|
33
|
+
desc %(Check i18n hygiene)
|
34
|
+
end
|
35
|
+
|
36
|
+
task(task_name => dependencies) do
|
37
|
+
checks = configure_checks(config)
|
38
|
+
|
39
|
+
checks.each do |check|
|
40
|
+
check.run do |result|
|
41
|
+
reporter.concat(result)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
reporter.report
|
46
|
+
|
47
|
+
exit(1) unless reporter.passed?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def relative_path_for(source_location)
|
54
|
+
source_location[0].gsub("#{pwd}/", "")
|
55
|
+
end
|
56
|
+
|
57
|
+
def reporter
|
58
|
+
@reporter ||= Reporter.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def configure_checks(config)
|
62
|
+
CHECKS.map do |check|
|
63
|
+
check.new(config)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def dependencies
|
68
|
+
[:environment] if defined?(Rails)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Hygiene
|
5
|
+
class Reporter
|
6
|
+
def concat(result)
|
7
|
+
print_progress(result)
|
8
|
+
|
9
|
+
results.push(result)
|
10
|
+
end
|
11
|
+
|
12
|
+
def results
|
13
|
+
@results ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def passed?
|
17
|
+
results.all? { |result| result.passed? }
|
18
|
+
end
|
19
|
+
|
20
|
+
def report
|
21
|
+
if passed?
|
22
|
+
puts Rainbow("\ni18n hygiene checks passed.").green
|
23
|
+
else
|
24
|
+
puts Rainbow("\ni18n hygiene checks failed.").red
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def result_color(result)
|
31
|
+
if result.passed?
|
32
|
+
:green
|
33
|
+
else
|
34
|
+
:red
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_progress(result)
|
39
|
+
print Rainbow(result.message).color(result_color(result))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module I18n
|
2
|
+
module Hygiene
|
3
|
+
class Result
|
4
|
+
attr_reader :message
|
5
|
+
|
6
|
+
def initialize(status, message: "")
|
7
|
+
@status = status
|
8
|
+
@message = message
|
9
|
+
end
|
10
|
+
|
11
|
+
def passed?
|
12
|
+
case @status
|
13
|
+
when :pass
|
14
|
+
true
|
15
|
+
when :failure
|
16
|
+
false
|
17
|
+
else
|
18
|
+
raise "Unsupported status"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -4,55 +4,29 @@ module I18n
|
|
4
4
|
# as defined in :en contains an interpolation variable, the value for that key as defined
|
5
5
|
# in any other locale must have a matching variable name.
|
6
6
|
class VariableChecker
|
7
|
-
|
8
|
-
NON_ENGLISH_LOCALES_TO_CHECK = [ :fr_fr ]
|
9
|
-
|
10
|
-
def initialize(key, i18n_wrapper)
|
7
|
+
def initialize(key, i18n_wrapper, primary_locale, locales = [])
|
11
8
|
@key = key
|
12
9
|
@i18n_wrapper = i18n_wrapper
|
10
|
+
@primary_locale = primary_locale
|
11
|
+
@locales = locales
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
16
|
-
|
17
|
-
if key_defined?(locale)
|
18
|
-
return true unless variables_match?(locale)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
false
|
22
|
-
end
|
23
|
-
|
24
|
-
def mismatch_details
|
25
|
-
if mismatched_variables_found?
|
26
|
-
details_array = []
|
27
|
-
NON_ENGLISH_LOCALES_TO_CHECK.each do |locale|
|
28
|
-
if key_defined?(locale)
|
29
|
-
details_array << mismatch_details_for_locale(locale) unless variables_match?(locale)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
return details_array.join("\n")
|
33
|
-
else
|
34
|
-
return "#{@key}: no missing interpolation variables found."
|
35
|
-
end
|
14
|
+
def mismatched_variables
|
15
|
+
@locales.each { |locale| yield locale, @key, missing_variables(locale) }
|
36
16
|
end
|
37
17
|
|
38
18
|
private
|
39
19
|
|
40
|
-
def mismatch_details_for_locale(locale)
|
41
|
-
"#{@key} for locale #{locale} is missing interpolation variable(s): #{missing_variables(locale)}"
|
42
|
-
end
|
43
|
-
|
44
20
|
def missing_variables(locale)
|
45
|
-
|
21
|
+
return [] unless key_defined?(locale)
|
22
|
+
|
23
|
+
variables(@primary_locale).reject { |v| variables(locale).include?(v) }
|
46
24
|
end
|
47
25
|
|
48
26
|
def key_defined?(locale)
|
49
27
|
@i18n_wrapper.key_found?(locale, @key)
|
50
28
|
end
|
51
29
|
|
52
|
-
def variables_match?(locale)
|
53
|
-
variables(locale) == variables(:en)
|
54
|
-
end
|
55
|
-
|
56
30
|
def variables(locale)
|
57
31
|
collect_variables(@i18n_wrapper.value(locale, @key))
|
58
32
|
end
|
data/lib/i18n/hygiene/wrapper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18n/hygiene/locale_translations'
|
3
|
+
|
1
4
|
module I18n
|
2
5
|
module Hygiene
|
3
6
|
# Utility class for interacting with i18n definitions. This is not intended to be used
|
@@ -5,12 +8,22 @@ module I18n
|
|
5
8
|
# queryable.
|
6
9
|
class Wrapper
|
7
10
|
|
8
|
-
def
|
9
|
-
|
11
|
+
def initialize(exclude_keys: [], exclude_scopes: [], locales: ::I18n.available_locales)
|
12
|
+
@locales = locales
|
13
|
+
@exclude_keys = exclude_keys
|
14
|
+
@exclude_scopes = exclude_scopes
|
15
|
+
end
|
16
|
+
|
17
|
+
def keys_to_check(locale)
|
18
|
+
I18n::Hygiene::LocaleTranslations.new(
|
19
|
+
translations: translations[locale],
|
20
|
+
exclude_keys: exclude_keys,
|
21
|
+
exclude_scopes: exclude_scopes
|
22
|
+
).keys_to_check
|
10
23
|
end
|
11
24
|
|
12
25
|
def locales
|
13
|
-
|
26
|
+
@locales
|
14
27
|
end
|
15
28
|
|
16
29
|
def key_found?(locale, key)
|
@@ -21,7 +34,7 @@ module I18n
|
|
21
34
|
|
22
35
|
def value(locale, key)
|
23
36
|
I18n.with_locale(locale) do
|
24
|
-
I18n.t(key)
|
37
|
+
I18n.t(key, resolve: false)
|
25
38
|
end
|
26
39
|
end
|
27
40
|
|
@@ -36,6 +49,14 @@ module I18n
|
|
36
49
|
::I18n.backend.send(:init_translations)
|
37
50
|
end
|
38
51
|
|
52
|
+
def exclude_keys
|
53
|
+
@exclude_keys
|
54
|
+
end
|
55
|
+
|
56
|
+
def exclude_scopes
|
57
|
+
@exclude_scopes
|
58
|
+
end
|
59
|
+
|
39
60
|
end
|
40
61
|
end
|
41
62
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-hygiene
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
+
- Eleanor Kiefel Haggerty
|
8
|
+
- Keith Pitty
|
7
9
|
- Nick Browne
|
8
|
-
- " Keith Pitty"
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2017-07-26 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: i18n
|
@@ -45,6 +46,40 @@ dependencies:
|
|
45
46
|
- - "~>"
|
46
47
|
- !ruby/object:Gem::Version
|
47
48
|
version: '1.3'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rainbow
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.99.1
|
56
|
+
- - "<"
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '3.0'
|
59
|
+
type: :runtime
|
60
|
+
prerelease: false
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.99.1
|
66
|
+
- - "<"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.8.7
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.8.7
|
48
83
|
- !ruby/object:Gem::Dependency
|
49
84
|
name: rspec
|
50
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,29 +94,57 @@ dependencies:
|
|
59
94
|
- - "~>"
|
60
95
|
- !ruby/object:Gem::Version
|
61
96
|
version: '3.0'
|
62
|
-
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-nav
|
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'
|
111
|
+
description: Provides a configurable rake task that checks locale data for likely
|
112
|
+
issues. Intended to be used in build pipelines to detect problems before they reach
|
113
|
+
production
|
63
114
|
email: dev@theconversation.edu.au
|
64
115
|
executables: []
|
65
116
|
extensions: []
|
66
|
-
extra_rdoc_files:
|
117
|
+
extra_rdoc_files:
|
118
|
+
- README.md
|
67
119
|
files:
|
120
|
+
- README.md
|
68
121
|
- lib/i18n/hygiene.rb
|
122
|
+
- lib/i18n/hygiene/checks/base.rb
|
123
|
+
- lib/i18n/hygiene/checks/html_entity.rb
|
124
|
+
- lib/i18n/hygiene/checks/key_usage.rb
|
125
|
+
- lib/i18n/hygiene/checks/missing_interpolation_variable.rb
|
126
|
+
- lib/i18n/hygiene/checks/script_tag.rb
|
127
|
+
- lib/i18n/hygiene/checks/unexpected_return_symbol.rb
|
128
|
+
- lib/i18n/hygiene/config.rb
|
129
|
+
- lib/i18n/hygiene/error_message_builder.rb
|
69
130
|
- lib/i18n/hygiene/key_usage_checker.rb
|
70
|
-
- lib/i18n/hygiene/keys_with_entities.rb
|
71
131
|
- lib/i18n/hygiene/keys_with_matched_value.rb
|
72
|
-
- lib/i18n/hygiene/keys_with_return_symbol.rb
|
73
|
-
- lib/i18n/hygiene/keys_with_script_tags.rb
|
74
132
|
- lib/i18n/hygiene/locale_translations.rb
|
75
|
-
- lib/i18n/hygiene/
|
133
|
+
- lib/i18n/hygiene/rake_task.rb
|
134
|
+
- lib/i18n/hygiene/reporter.rb
|
135
|
+
- lib/i18n/hygiene/result.rb
|
76
136
|
- lib/i18n/hygiene/variable_checker.rb
|
77
137
|
- lib/i18n/hygiene/wrapper.rb
|
78
|
-
- lib/tasks/i18n_hygiene.rake
|
79
138
|
homepage: https://github.com/conversation/i18n-hygiene
|
80
139
|
licenses:
|
81
140
|
- MIT
|
82
141
|
metadata: {}
|
83
142
|
post_install_message:
|
84
|
-
rdoc_options:
|
143
|
+
rdoc_options:
|
144
|
+
- "--title"
|
145
|
+
- i18n-hygiene documentation
|
146
|
+
- "--main"
|
147
|
+
- README.md
|
85
148
|
require_paths:
|
86
149
|
- lib
|
87
150
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -96,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
159
|
version: '0'
|
97
160
|
requirements: []
|
98
161
|
rubyforge_project:
|
99
|
-
rubygems_version: 2.
|
162
|
+
rubygems_version: 2.5.2
|
100
163
|
signing_key:
|
101
164
|
specification_version: 4
|
102
|
-
summary:
|
165
|
+
summary: A linter for translation data in ruby applications
|
103
166
|
test_files: []
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'enumerator'
|
2
|
-
|
3
|
-
module I18n
|
4
|
-
module Hygiene
|
5
|
-
# A collection of Strings that indicate the i18n keys in any locale that contain
|
6
|
-
# entities which are likely to be rendered incorrectly. Only keys ending in _html
|
7
|
-
# or _markdown may contain entities.
|
8
|
-
#
|
9
|
-
class KeysWithEntities
|
10
|
-
include Enumerable
|
11
|
-
|
12
|
-
ENTITY_REGEX = /&\w+;/
|
13
|
-
|
14
|
-
def initialize(i18nwrapper: nil)
|
15
|
-
@matcher = I18n::Hygiene::KeysWithMatchedValue.new(ENTITY_REGEX, i18nwrapper, reject_keys: reject_keys)
|
16
|
-
end
|
17
|
-
|
18
|
-
def each(&block)
|
19
|
-
@matcher.each(&block)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def reject_keys
|
25
|
-
Proc.new { |key| key.end_with?("_html") || key.end_with?("_markdown") }
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'enumerator'
|
2
|
-
|
3
|
-
module I18n
|
4
|
-
module Hygiene
|
5
|
-
# Checks to see if any i18n values contain the U+23CE character.
|
6
|
-
# It has been included in PhraseApp translations but is unwanted.
|
7
|
-
class KeysWithReturnSymbol
|
8
|
-
include Enumerable
|
9
|
-
|
10
|
-
RETURN_SYMBOL_REGEX = /\u23ce/
|
11
|
-
|
12
|
-
def initialize(i18n_wrapper: nil)
|
13
|
-
@matcher = I18n::Hygiene::KeysWithMatchedValue.new(RETURN_SYMBOL_REGEX, i18n_wrapper)
|
14
|
-
end
|
15
|
-
|
16
|
-
def each(&block)
|
17
|
-
@matcher.each(&block)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'enumerator'
|
2
|
-
|
3
|
-
module I18n
|
4
|
-
module Hygiene
|
5
|
-
# Checks to see if any i18n values contain script tags.
|
6
|
-
# We should never allow script tags!
|
7
|
-
class KeysWithScriptTags
|
8
|
-
include Enumerable
|
9
|
-
|
10
|
-
SCRIPT_TAG_REGEX = /<script>.*<\/script>/
|
11
|
-
|
12
|
-
def initialize(i18n_wrapper: nil)
|
13
|
-
@matcher = I18n::Hygiene::KeysWithMatchedValue.new(SCRIPT_TAG_REGEX, i18n_wrapper)
|
14
|
-
end
|
15
|
-
|
16
|
-
def each(&block)
|
17
|
-
@matcher.each(&block)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/i18n/hygiene/railtie.rb
DELETED
data/lib/tasks/i18n_hygiene.rake
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
namespace :i18n do
|
2
|
-
namespace :hygiene do
|
3
|
-
|
4
|
-
desc 'run all the i18n hygiene checks'
|
5
|
-
task all: [:check_key_usage, :check_variables, :check_entities, :check_return_symbols, :check_script_tags]
|
6
|
-
|
7
|
-
desc "check usage of all EN keys"
|
8
|
-
task check_key_usage: :environment do
|
9
|
-
require 'parallel'
|
10
|
-
|
11
|
-
puts "Checking usage of EN keys..."
|
12
|
-
puts "(Please be patient while the codebase is searched for key usage)"
|
13
|
-
|
14
|
-
unused_keys = Parallel.map(I18n::Hygiene::Wrapper.new.keys_to_check) { |key|
|
15
|
-
key unless I18n::Hygiene::KeyUsageChecker.new(key).used_in_codebase?
|
16
|
-
}.compact
|
17
|
-
|
18
|
-
unused_keys.each do |key|
|
19
|
-
puts "#{key} is unused."
|
20
|
-
end
|
21
|
-
|
22
|
-
puts "Finished checking.\n\n"
|
23
|
-
|
24
|
-
exit(1) if unused_keys.any?
|
25
|
-
end
|
26
|
-
|
27
|
-
desc "check for mismatching interpolation variables"
|
28
|
-
task check_variables: :environment do
|
29
|
-
puts "Checking for mismatching interpolation variables..."
|
30
|
-
|
31
|
-
wrapper = I18n::Hygiene::Wrapper.new
|
32
|
-
|
33
|
-
mismatched_variables = wrapper.keys_to_check.select do |key|
|
34
|
-
checker = I18n::Hygiene::VariableChecker.new(key, wrapper)
|
35
|
-
checker.mismatch_details if checker.mismatched_variables_found?
|
36
|
-
end
|
37
|
-
|
38
|
-
mismatched_variables.each { |details| puts details }
|
39
|
-
|
40
|
-
puts "Finished checking.\n\n"
|
41
|
-
|
42
|
-
exit(1) if mismatched_variables.any?
|
43
|
-
end
|
44
|
-
|
45
|
-
desc "check for i18n phrases that contain entities"
|
46
|
-
task check_entities: :environment do
|
47
|
-
puts "Checking for phrases that contain entities but probably shouldn't..."
|
48
|
-
|
49
|
-
keys_with_entities = I18n::Hygiene::KeysWithEntities.new
|
50
|
-
|
51
|
-
keys_with_entities.each do |key|
|
52
|
-
puts "- #{key}"
|
53
|
-
end
|
54
|
-
|
55
|
-
puts "Finished checking.\n\n"
|
56
|
-
|
57
|
-
exit(1) if keys_with_entities.any?
|
58
|
-
end
|
59
|
-
|
60
|
-
desc "Check there are no values containing return symbols"
|
61
|
-
task check_return_symbols: :environment do
|
62
|
-
puts "Checking that no values contain return symbols i.e. U+23CE ..."
|
63
|
-
|
64
|
-
keys_with_return_symbols = I18n::Hygiene::KeysWithReturnSymbol.new
|
65
|
-
|
66
|
-
keys_with_return_symbols.each { |key| puts "- #{key}" }
|
67
|
-
|
68
|
-
puts "Finished checking.\n\n"
|
69
|
-
|
70
|
-
exit(1) if keys_with_return_symbols.any?
|
71
|
-
end
|
72
|
-
|
73
|
-
desc "Check there are no values containing scripts"
|
74
|
-
task check_script_tags: :environment do
|
75
|
-
puts "Checking that no values contain script tags ..."
|
76
|
-
|
77
|
-
keys_with_script_tags = I18n::Hygiene::KeysWithScriptTags.new
|
78
|
-
|
79
|
-
keys_with_script_tags.each { |key| puts " - #{key}" }
|
80
|
-
|
81
|
-
puts "Finished checking.\n\n"
|
82
|
-
|
83
|
-
exit(1) if keys_with_script_tags.any?
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|