i18n-tasks 0.7.6 → 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +4 -0
- data/README.md +4 -8
- data/bin/i18n-tasks +3 -3
- data/config/locales/en.yml +77 -77
- data/config/locales/ru.yml +78 -78
- data/i18n-tasks.gemspec +3 -3
- data/lib/i18n/tasks/command/options/trees.rb +1 -1
- data/lib/i18n/tasks/console_context.rb +1 -1
- data/lib/i18n/tasks/data/file_formats.rb +1 -1
- data/lib/i18n/tasks/data/tree/node.rb +10 -10
- data/lib/i18n/tasks/data/tree/nodes.rb +9 -3
- data/lib/i18n/tasks/data/tree/siblings.rb +22 -18
- data/lib/i18n/tasks/data/tree/traversal.rb +5 -1
- data/lib/i18n/tasks/logging.rb +1 -1
- data/lib/i18n/tasks/split_key.rb +60 -56
- data/lib/i18n/tasks/version.rb +1 -1
- data/spec/i18n_tasks_spec.rb +27 -1
- data/spec/locale_tree/siblings_spec.rb +5 -0
- data/spec/split_key_spec.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4b9791c830f1768141d6d43dba65b1e3b963e90
|
4
|
+
data.tar.gz: ddc91d95b7bdf1a0590af2c4175d71ac5a896fa7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd0ea56aef3a916fd7f1a1ae6f51bdd83b01b69aa66f9c9ed8a38e0f82e3d59550443ac116a731227b8330f2866fc8eb69fca8841d2731c013a681f2021f9f13
|
7
|
+
data.tar.gz: b4f2afc081cda21da193205db3d5f4555e41cb986c47cb9f85a29387fee4daf81d99c41436fd3a7c6be4c5510ac8f23f836fb864432d66aca96d98fb5f544452
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -2,23 +2,19 @@
|
|
2
2
|
|
3
3
|
i18n-tasks helps you find and manage missing and unused translations.
|
4
4
|
|
5
|
-
|
5
|
+
<img width="539" height="331" src="https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-tasks.png">
|
6
6
|
|
7
|
-
|
7
|
+
This gem analyses code statically for key usages, such as `I18n.t('some.key')`, in order to:
|
8
8
|
|
9
9
|
* Report keys that are missing or unused.
|
10
10
|
* Pre-fill missing keys, optionally from Google Translate.
|
11
11
|
* Remove unused keys.
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
This gem addresses these drawbacks of [i18n gem][i18n-gem] design:
|
13
|
+
Thus addressing the two main problems of [i18n gem][i18n-gem] design:
|
16
14
|
|
17
15
|
* Missing keys only blow up at runtime.
|
18
16
|
* Keys no longer in use may accumulate and introduce overhead, without you knowing it.
|
19
17
|
|
20
|
-
<img width="539" height="331" src="https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-tasks.png">
|
21
|
-
|
22
18
|
## Installation
|
23
19
|
|
24
20
|
i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails), or similar, even if it isn't ruby.
|
@@ -26,7 +22,7 @@ i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails
|
|
26
22
|
Add it to the Gemfile:
|
27
23
|
|
28
24
|
```ruby
|
29
|
-
gem 'i18n-tasks', '~> 0.7.
|
25
|
+
gem 'i18n-tasks', '~> 0.7.7'
|
30
26
|
```
|
31
27
|
|
32
28
|
Copy default [configuration file](#configuration) (optional):
|
data/bin/i18n-tasks
CHANGED
@@ -13,10 +13,10 @@ require 'i18n/tasks/commands'
|
|
13
13
|
require 'slop'
|
14
14
|
|
15
15
|
err = proc { |message, exit_code|
|
16
|
-
if
|
17
|
-
|
16
|
+
if $stderr.isatty
|
17
|
+
$stderr.puts Term::ANSIColor.yellow('i18n-tasks: ' + message)
|
18
18
|
else
|
19
|
-
|
19
|
+
$stderr.puts message
|
20
20
|
end
|
21
21
|
exit exit_code
|
22
22
|
}
|
data/config/locales/en.yml
CHANGED
@@ -1,102 +1,102 @@
|
|
1
1
|
---
|
2
2
|
en:
|
3
3
|
i18n_tasks:
|
4
|
-
common:
|
5
|
-
locale: Locale
|
6
|
-
type: Type
|
7
|
-
key: Key
|
8
|
-
value: Value
|
9
|
-
base_value: Base Value
|
10
|
-
details: Details
|
11
|
-
continue_q: Continue?
|
12
|
-
n_more: "%{count} more"
|
13
|
-
google_translate:
|
14
|
-
errors:
|
15
|
-
no_results: Google Translate returned no results. Make sure billing information is set at
|
16
|
-
https://code.google.com/apis/console.
|
17
|
-
remove_unused:
|
18
|
-
confirm:
|
19
|
-
one: One translations will be removed from %{locales}.
|
20
|
-
other: "%{count} translation will be removed from %{locales}."
|
21
|
-
removed: Removed %{count} keys
|
22
|
-
noop: No unused keys to remove
|
23
|
-
translate_missing:
|
24
|
-
translated: Translated %{count} keys
|
25
4
|
add_missing:
|
26
5
|
added: Added %{count} keys
|
27
|
-
unused:
|
28
|
-
none: Every translation is in use.
|
29
|
-
missing:
|
30
|
-
none: No translations are missing.
|
31
|
-
usages:
|
32
|
-
none: No key usages found.
|
33
|
-
health:
|
34
|
-
no_keys_detected: No keys detected. Check data.read in config/i18n-tasks.yml.
|
35
|
-
data_stats:
|
36
|
-
title: Forest (%{locales})
|
37
|
-
text: has %{key_count} keys across %{locale_count} locales. On average, values are %{value_chars_avg}
|
38
|
-
characters long, keys have %{key_segments_avg} segments, a locale has %{per_locale_avg} keys.
|
39
|
-
text_single_locale: has %{key_count} keys in total. On average, values are %{value_chars_avg}
|
40
|
-
characters long, keys have %{key_segments_avg} segments.
|
41
6
|
cmd:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
7
|
+
args:
|
8
|
+
default_all: 'Default: all'
|
9
|
+
default_text: 'Default: %{value}'
|
10
|
+
desc:
|
11
|
+
confirm: Confirm automatically
|
12
|
+
data_format: 'Data format: %{valid_text}. %{default_text}.'
|
13
|
+
key_pattern: Filter by key pattern (e.g. 'common.*')
|
14
|
+
key_pattern_to_rename: Full key (pattern) to rename. Required
|
15
|
+
keys: List of keys separated by commas (,), spaces, or newlines.
|
16
|
+
locale: 'Locale. Default: base'
|
17
|
+
locale_to_translate_from: 'Locale to translate from (default: base)'
|
18
|
+
locales_filter: 'Comma-separated list of locale(s) to process. Default: all. Special: base.'
|
19
|
+
missing_types: 'Filter by types: %{valid}. Default: all'
|
20
|
+
new_key_name: New name, interpolates original name as %{key}. Required
|
21
|
+
nostdin: Do not read from stdin
|
22
|
+
out_format: 'Output format: %{valid_text}. %{default_text}.'
|
23
|
+
pattern_router: 'Use pattern router: keys moved per config data.write'
|
24
|
+
strict: Do not infer dynamic key usage such as `t("category.\#{category.name}")`
|
25
|
+
value: 'Value. Interpolates: %{value}, %{human_key}, %{value_or_human_key}'
|
46
26
|
desc:
|
47
|
-
|
27
|
+
add_missing: add missing keys to locale data
|
28
|
+
config: display i18n-tasks configuration
|
48
29
|
data: show locale data
|
49
30
|
data_merge: merge locale data with trees
|
50
|
-
data_write: replace locale data with tree
|
51
31
|
data_remove: remove keys present in tree from data
|
52
|
-
|
32
|
+
data_write: replace locale data with tree
|
33
|
+
eq_base: show translations equal to base value
|
53
34
|
find: show where keys are used in the code
|
54
|
-
|
35
|
+
gem_path: show path to the gem
|
36
|
+
health: is everything OK?
|
37
|
+
irb: start REPL session within i18n-tasks context
|
55
38
|
missing: show missing translations
|
56
|
-
|
57
|
-
add_missing: add missing keys to locale data
|
39
|
+
normalize: 'normalize translation data: sort and move to the right files'
|
58
40
|
remove_unused: remove unused keys
|
59
|
-
|
60
|
-
|
61
|
-
tree_merge: merge trees
|
41
|
+
translate_missing: translate missing keys with Google Translate
|
42
|
+
tree_convert: convert tree between formats
|
62
43
|
tree_filter: filter tree by key pattern
|
44
|
+
tree_merge: merge trees
|
63
45
|
tree_rename_key: rename tree node
|
64
|
-
tree_subtract: tree A minus the keys in tree B
|
65
46
|
tree_set_value: set values of keys, optionally match a pattern
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
irb: start REPL session within i18n-tasks context
|
47
|
+
tree_subtract: tree A minus the keys in tree B
|
48
|
+
tree_translate: Google Translate a tree to root locales
|
49
|
+
unused: show unused translations
|
70
50
|
xlsx_report: save missing and unused translations to an Excel file
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
out_format: 'Output format: %{valid_text}. %{default_text}.'
|
76
|
-
data_format: 'Data format: %{valid_text}. %{default_text}.'
|
77
|
-
keys: List of keys separated by commas (,), spaces, or newlines.
|
78
|
-
locales_filter: 'Comma-separated list of locale(s) to process. Default: all. Special: base.'
|
79
|
-
locale: 'Locale. Default: base'
|
80
|
-
locale_to_translate_from: 'Locale to translate from (default: base)'
|
81
|
-
confirm: Confirm automatically
|
82
|
-
nostdin: Do not read from stdin
|
83
|
-
strict: Do not infer dynamic key usage such as `t("category.\#{category.name}")`
|
84
|
-
missing_types: 'Filter by types: %{valid}. Default: all'
|
85
|
-
key_pattern: Filter by key pattern (e.g. 'common.*')
|
86
|
-
key_pattern_to_rename: Full key (pattern) to rename. Required
|
87
|
-
new_key_name: New name, interpolates original name as %{key}. Required
|
88
|
-
value: 'Value. Interpolates: %{value}, %{human_key}, %{value_or_human_key}'
|
89
|
-
pattern_router: 'Use pattern router: keys moved per config data.write'
|
90
|
-
enum_opt:
|
91
|
-
desc: "%{valid_text}. %{default_text}"
|
92
|
-
invalid: "%{invalid} is not one of: %{valid}."
|
51
|
+
encourage:
|
52
|
+
- Good job!
|
53
|
+
- Well done!
|
54
|
+
- Perfect!
|
93
55
|
enum_list_opt:
|
94
56
|
desc: 'Comma-separated list of: %{valid_text}. %{default_text}'
|
95
57
|
invalid: "%{invalid} is not in: %{valid}."
|
58
|
+
enum_opt:
|
59
|
+
desc: "%{valid_text}. %{default_text}"
|
60
|
+
invalid: "%{invalid} is not one of: %{valid}."
|
96
61
|
errors:
|
97
|
-
pass_forest: Pass locale forest
|
98
|
-
invalid_locale: Invalid locale %{invalid}
|
99
62
|
invalid_format: 'Unknown format %{invalid}. Valid: %{valid}.'
|
63
|
+
invalid_locale: Invalid locale %{invalid}
|
100
64
|
invalid_missing_type:
|
101
65
|
one: 'Unknown type %{invalid}. Valid: %{valid}.'
|
102
66
|
other: 'Unknown types: %{invalid}. Valid: %{valid}.'
|
67
|
+
pass_forest: Pass locale forest
|
68
|
+
common:
|
69
|
+
base_value: Base Value
|
70
|
+
continue_q: Continue?
|
71
|
+
details: Details
|
72
|
+
key: Key
|
73
|
+
locale: Locale
|
74
|
+
n_more: "%{count} more"
|
75
|
+
type: Type
|
76
|
+
value: Value
|
77
|
+
data_stats:
|
78
|
+
text: has %{key_count} keys across %{locale_count} locales. On average, values are %{value_chars_avg}
|
79
|
+
characters long, keys have %{key_segments_avg} segments, a locale has %{per_locale_avg} keys.
|
80
|
+
text_single_locale: has %{key_count} keys in total. On average, values are %{value_chars_avg}
|
81
|
+
characters long, keys have %{key_segments_avg} segments.
|
82
|
+
title: Forest (%{locales})
|
83
|
+
google_translate:
|
84
|
+
errors:
|
85
|
+
no_results: Google Translate returned no results. Make sure billing information is set at
|
86
|
+
https://code.google.com/apis/console.
|
87
|
+
health:
|
88
|
+
no_keys_detected: No keys detected. Check data.read in config/i18n-tasks.yml.
|
89
|
+
missing:
|
90
|
+
none: No translations are missing.
|
91
|
+
remove_unused:
|
92
|
+
confirm:
|
93
|
+
one: One translations will be removed from %{locales}.
|
94
|
+
other: "%{count} translation will be removed from %{locales}."
|
95
|
+
noop: No unused keys to remove
|
96
|
+
removed: Removed %{count} keys
|
97
|
+
translate_missing:
|
98
|
+
translated: Translated %{count} keys
|
99
|
+
unused:
|
100
|
+
none: Every translation is in use.
|
101
|
+
usages:
|
102
|
+
none: No key usages found.
|
data/config/locales/ru.yml
CHANGED
@@ -1,103 +1,103 @@
|
|
1
1
|
---
|
2
2
|
ru:
|
3
3
|
i18n_tasks:
|
4
|
-
common:
|
5
|
-
locale: "Язык"
|
6
|
-
type: "Тип"
|
7
|
-
key: "Ключ"
|
8
|
-
value: "Значение"
|
9
|
-
base_value: "Исходное значение"
|
10
|
-
details: "Детали"
|
11
|
-
continue_q: "Продолжить?"
|
12
|
-
n_more: "ещё %{count}"
|
13
|
-
google_translate:
|
14
|
-
errors:
|
15
|
-
no_results: Google Translate не дал результатов. Убедитесь в том, что платежная информация
|
16
|
-
добавлена в в https://code.google.com/apis/console.
|
17
|
-
remove_unused:
|
18
|
-
confirm:
|
19
|
-
one: "Один перевод будут удалён из %{locales}."
|
20
|
-
other: "Переводы (%{count}) будут удалены из %{locales}."
|
21
|
-
removed: "Удалены ключи (%{count})"
|
22
|
-
noop: "Нет неиспользуемых ключей"
|
23
|
-
translate_missing:
|
24
|
-
translated: "Переведены ключи (%{count})"
|
25
4
|
add_missing:
|
26
5
|
added: "Добавлены ключи (%{count})"
|
27
|
-
unused:
|
28
|
-
none: "Все переводы используются."
|
29
|
-
missing:
|
30
|
-
none: "Всё переведено."
|
31
|
-
usages:
|
32
|
-
none: "Не найдено использований."
|
33
|
-
health:
|
34
|
-
no_keys_detected: "Ключи не обнаружены. Проверьте data.read в config/i18n-tasks.yml."
|
35
|
-
data_stats:
|
36
|
-
title: "Данные (%{locales}):"
|
37
|
-
text: "%{key_count} ключей в %{locale_count} языках. В среднем, длина строки: %{value_chars_avg},
|
38
|
-
сегменты ключей: %{key_segments_avg}, ключей в языке %{per_locale_avg}."
|
39
|
-
text_single_locale: "%{key_count} ключей. В среднем, длина строки: %{value_chars_avg}, сегменты
|
40
|
-
ключей: %{key_segments_avg}."
|
41
6
|
cmd:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
7
|
+
args:
|
8
|
+
default_all: "По умолчанию: все"
|
9
|
+
default_text: "По умолчанию: %{value}"
|
10
|
+
desc:
|
11
|
+
confirm: "Подтвердить автоматом"
|
12
|
+
data_format: "Формат данных: %{valid_text}. %{default_text}."
|
13
|
+
key_pattern: "Маска ключа (например, common.*)"
|
14
|
+
key_pattern_to_rename: "Полный ключ (шаблон) для переименования. Необходимый параметр."
|
15
|
+
keys: "Список ключей, разделенных запятыми (,), пробелами или символами новой строки."
|
16
|
+
locale: "Язык. По умолчанию: base"
|
17
|
+
locale_to_translate_from: "Язык, с которого переводить (по умолчанию: base)"
|
18
|
+
locales_filter: "Список языков для обработки, разделенный запятыми (,). По умолчанию: все.
|
19
|
+
Специальное значение: base."
|
20
|
+
missing_types: "Типы недостающих переводов: %{valid}. По умолчанию: все"
|
21
|
+
new_key_name: "Новое имя, интерполирует оригинальное название как %{key}. Необходимый параметр."
|
22
|
+
nostdin: "Не читать дерево из стандартного ввода"
|
23
|
+
out_format: "Формат вывода: %{valid_text}. %{default_text}."
|
24
|
+
pattern_router: "Использовать pattern_router: ключи распределятся по файлам согласно data.write"
|
25
|
+
strict: Не угадывать динамические использования ключей, например `t("category.#{category.key}")`
|
26
|
+
value: "Значение, интерполируется с %{value}, %{human_key}, %{value_or_human_key}"
|
46
27
|
desc:
|
47
|
-
|
28
|
+
add_missing: "добавить недостающие ключи к переводам"
|
29
|
+
config: "показать конфигурацию"
|
48
30
|
data: "показать данные переводов"
|
49
31
|
data_merge: "добавить дерево к переводам"
|
50
|
-
data_write: "заменить переводы деревом"
|
51
32
|
data_remove: "удалить ключи, которые есть в дереве, из данных"
|
52
|
-
|
33
|
+
data_write: "заменить переводы деревом"
|
34
|
+
eq_base: "показать переводы, равные значениям в основном языке"
|
53
35
|
find: "показать, где ключи используются в коде"
|
54
|
-
|
36
|
+
gem_path: "показать путь к ruby gem"
|
37
|
+
health: "Всё ОК?"
|
38
|
+
irb: "начать REPL сессию в контексте i18n-tasks"
|
55
39
|
missing: "показать недостающие переводы"
|
56
|
-
|
57
|
-
add_missing: "добавить недостающие ключи к переводам"
|
40
|
+
normalize: "нормализовать файлы переводов (сортировка и распределение)"
|
58
41
|
remove_unused: "удалить неиспользуемые ключи"
|
59
|
-
|
60
|
-
|
42
|
+
translate_missing: "перевести недостающие переводы с Google Translate"
|
43
|
+
tree_convert: "преобразовать дерево между форматами"
|
61
44
|
tree_filter: "фильтровать дерево по ключу"
|
45
|
+
tree_merge: "объединенить деревья"
|
62
46
|
tree_rename_key: "переименовать узел дерева"
|
63
|
-
tree_subtract: "дерево A минус ключи в дереве B"
|
64
47
|
tree_set_value: "заменить значения ключей"
|
65
|
-
|
66
|
-
config: "показать конфигурацию"
|
67
|
-
gem_path: "показать путь к ruby gem"
|
68
|
-
irb: "начать REPL сессию в контексте i18n-tasks"
|
69
|
-
xlsx_report: "сохранить недостающие и неиспользуемые переводы в Excel-файл"
|
48
|
+
tree_subtract: "дерево A минус ключи в дереве B"
|
70
49
|
tree_translate: "Перевести дерево при помощи Google Translate на язык корневых узлов"
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
keys: "Список ключей, разделенных запятыми (,), пробелами или символами новой строки."
|
78
|
-
locales_filter: "Список языков для обработки, разделенный запятыми (,). По умолчанию: все.
|
79
|
-
Специальное значение: base."
|
80
|
-
locale_to_translate_from: "Язык, с которого переводить (по умолчанию: base)"
|
81
|
-
locale: "Язык. По умолчанию: base"
|
82
|
-
confirm: "Подтвердить автоматом"
|
83
|
-
nostdin: "Не читать дерево из стандартного ввода"
|
84
|
-
strict: Не угадывать динамические использования ключей, например `t("category.#{category.key}")`
|
85
|
-
missing_types: "Типы недостающих переводов: %{valid}. По умолчанию: все"
|
86
|
-
key_pattern: "Маска ключа (например, common.*)"
|
87
|
-
key_pattern_to_rename: "Полный ключ (шаблон) для переименования. Необходимый параметр."
|
88
|
-
new_key_name: "Новое имя, интерполирует оригинальное название как %{key}. Необходимый параметр."
|
89
|
-
value: "Значение, интерполируется с %{value}, %{human_key}, %{value_or_human_key}"
|
90
|
-
pattern_router: "Использовать pattern_router: ключи распределятся по файлам согласно data.write"
|
91
|
-
enum_opt:
|
92
|
-
desc: "%{valid_text}. %{default_text}"
|
93
|
-
invalid: "%{invalid} не является одним из: %{valid}."
|
50
|
+
unused: "показать неиспользуемые переводы"
|
51
|
+
xlsx_report: "сохранить недостающие и неиспользуемые переводы в Excel-файл"
|
52
|
+
encourage:
|
53
|
+
- "Хорошая работа!"
|
54
|
+
- "Отлично!"
|
55
|
+
- "Прекрасно!"
|
94
56
|
enum_list_opt:
|
95
57
|
desc: "Разделенных запятыми список: %{valid_text}. %{default_text}"
|
96
58
|
invalid: "%{invalid} не в: %{valid}."
|
59
|
+
enum_opt:
|
60
|
+
desc: "%{valid_text}. %{default_text}"
|
61
|
+
invalid: "%{invalid} не является одним из: %{valid}."
|
97
62
|
errors:
|
98
|
-
pass_forest: "Передайте дерево"
|
99
|
-
invalid_locale: "Неверный язык %{invalid}"
|
100
63
|
invalid_format: "Неизвестный формат %{invalid}. Форматы: %{valid}."
|
64
|
+
invalid_locale: "Неверный язык %{invalid}"
|
101
65
|
invalid_missing_type:
|
102
66
|
one: "Неизвестный тип %{invalid}. Типы: %{valid}."
|
103
67
|
other: "Неизвестные типы: %{invalid}. Типы: %{valid}."
|
68
|
+
pass_forest: "Передайте дерево"
|
69
|
+
common:
|
70
|
+
base_value: "Исходное значение"
|
71
|
+
continue_q: "Продолжить?"
|
72
|
+
details: "Детали"
|
73
|
+
key: "Ключ"
|
74
|
+
locale: "Язык"
|
75
|
+
n_more: "ещё %{count}"
|
76
|
+
type: "Тип"
|
77
|
+
value: "Значение"
|
78
|
+
data_stats:
|
79
|
+
text: "%{key_count} ключей в %{locale_count} языках. В среднем, длина строки: %{value_chars_avg},
|
80
|
+
сегменты ключей: %{key_segments_avg}, ключей в языке %{per_locale_avg}."
|
81
|
+
text_single_locale: "%{key_count} ключей. В среднем, длина строки: %{value_chars_avg}, сегменты
|
82
|
+
ключей: %{key_segments_avg}."
|
83
|
+
title: "Данные (%{locales}):"
|
84
|
+
google_translate:
|
85
|
+
errors:
|
86
|
+
no_results: Google Translate не дал результатов. Убедитесь в том, что платежная информация
|
87
|
+
добавлена в в https://code.google.com/apis/console.
|
88
|
+
health:
|
89
|
+
no_keys_detected: "Ключи не обнаружены. Проверьте data.read в config/i18n-tasks.yml."
|
90
|
+
missing:
|
91
|
+
none: "Всё переведено."
|
92
|
+
remove_unused:
|
93
|
+
confirm:
|
94
|
+
one: "Один перевод будут удалён из %{locales}."
|
95
|
+
other: "Переводы (%{count}) будут удалены из %{locales}."
|
96
|
+
noop: "Нет неиспользуемых ключей"
|
97
|
+
removed: "Удалены ключи (%{count})"
|
98
|
+
translate_missing:
|
99
|
+
translated: "Переведены ключи (%{count})"
|
100
|
+
unused:
|
101
|
+
none: "Все переводы используются."
|
102
|
+
usages:
|
103
|
+
none: "Не найдено использований."
|
data/i18n-tasks.gemspec
CHANGED
@@ -12,15 +12,15 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.description = <<-TEXT
|
13
13
|
i18n-tasks helps you find and manage missing and unused translations.
|
14
14
|
|
15
|
-
It
|
16
|
-
|
15
|
+
It analyses code statically for key usages, such as `I18n.t('some.key')`, in order to report keys that are missing or unused,
|
16
|
+
pre-fill missing keys (optionally from Google Translate), and remove unused keys.
|
17
|
+
well.
|
17
18
|
TEXT
|
18
19
|
s.post_install_message = <<-TEXT
|
19
20
|
# Install default configuration:
|
20
21
|
cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
21
22
|
# Add an RSpec for missing and unused keys:
|
22
23
|
cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
23
|
-
}
|
24
24
|
TEXT
|
25
25
|
s.homepage = 'https://github.com/glebm/i18n-tasks'
|
26
26
|
if s.respond_to?(:metadata=)
|
@@ -40,7 +40,7 @@ module I18n
|
|
40
40
|
def write_tree(path, tree)
|
41
41
|
::FileUtils.mkpath(File.dirname path)
|
42
42
|
::File.open(path, 'w') { |f|
|
43
|
-
f.write adapter_dump(tree.to_hash, self.class.adapter_name_for_path(path))
|
43
|
+
f.write adapter_dump(tree.to_hash(true), self.class.adapter_name_for_path(path))
|
44
44
|
}
|
45
45
|
end
|
46
46
|
|
@@ -127,9 +127,9 @@ module I18n::Tasks::Data::Tree
|
|
127
127
|
parent && parent.children || Siblings.new(nodes: [self])
|
128
128
|
end
|
129
129
|
|
130
|
-
def to_hash
|
131
|
-
@hash ||= begin
|
132
|
-
children_hash =
|
130
|
+
def to_hash(sort = false)
|
131
|
+
(@hash ||= {})[sort] ||= begin
|
132
|
+
children_hash = children ? children.to_hash(sort) : {}
|
133
133
|
if key.nil?
|
134
134
|
children_hash
|
135
135
|
elsif leaf?
|
@@ -144,13 +144,13 @@ module I18n::Tasks::Data::Tree
|
|
144
144
|
delegate :to_yaml, to: :to_hash
|
145
145
|
|
146
146
|
def inspect(level = 0)
|
147
|
-
if key.nil?
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
(": #{Term::ANSIColor.cyan(
|
152
|
-
(" #{
|
153
|
-
|
147
|
+
label = if key.nil?
|
148
|
+
Term::ANSIColor.dark '∅'
|
149
|
+
else
|
150
|
+
[Term::ANSIColor.color(1 + level % 15, key),
|
151
|
+
(": #{Term::ANSIColor.cyan(value.to_s)}" if leaf?),
|
152
|
+
(" #{data}" if data?)].compact.join
|
153
|
+
end
|
154
154
|
[' ' * level, label, ("\n" + children.map { |c| c.inspect(level + 1) }.join("\n") if children?)].compact.join
|
155
155
|
end
|
156
156
|
|
@@ -29,8 +29,14 @@ module I18n::Tasks::Data::Tree
|
|
29
29
|
self.class.new(attr)
|
30
30
|
end
|
31
31
|
|
32
|
-
def to_hash
|
33
|
-
@hash ||=
|
32
|
+
def to_hash(sort = false)
|
33
|
+
(@hash ||= {})[sort] ||= begin
|
34
|
+
if sort
|
35
|
+
self.sort { |a, b| a.key <=> b.key }
|
36
|
+
else
|
37
|
+
self
|
38
|
+
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
39
|
+
end
|
34
40
|
end
|
35
41
|
|
36
42
|
delegate :to_json, to: :to_hash
|
@@ -40,7 +46,7 @@ module I18n::Tasks::Data::Tree
|
|
40
46
|
if present?
|
41
47
|
map(&:inspect) * "\n"
|
42
48
|
else
|
43
|
-
Term::ANSIColor.dark '∅'
|
49
|
+
Term::ANSIColor.dark '{∅}'
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
@@ -45,7 +45,7 @@ module I18n::Tasks::Data::Tree
|
|
45
45
|
key_to_node[new_node.key] = new_node
|
46
46
|
end
|
47
47
|
|
48
|
-
include SplitKey
|
48
|
+
include ::I18n::Tasks::SplitKey
|
49
49
|
|
50
50
|
# @return [Node] by full key
|
51
51
|
def get(full_key)
|
@@ -110,22 +110,7 @@ module I18n::Tasks::Data::Tree
|
|
110
110
|
def merge!(nodes)
|
111
111
|
nodes = Siblings.from_nested_hash(nodes) if nodes.is_a?(Hash)
|
112
112
|
nodes.each do |node|
|
113
|
-
|
114
|
-
our = key_to_node[node.key]
|
115
|
-
next if our == node
|
116
|
-
our.value = node.value if node.leaf?
|
117
|
-
our.data.merge!(node.data) if node.data?
|
118
|
-
if node.children?
|
119
|
-
if our.children
|
120
|
-
our.children.merge!(node.children)
|
121
|
-
else
|
122
|
-
warn_add_children_to_leaf our
|
123
|
-
our.children = node.children
|
124
|
-
end
|
125
|
-
end
|
126
|
-
else
|
127
|
-
key_to_node[node.key] = node.derive(parent: parent)
|
128
|
-
end
|
113
|
+
merge_node! node
|
129
114
|
end
|
130
115
|
@list = key_to_node.values
|
131
116
|
dirty!
|
@@ -161,12 +146,31 @@ module I18n::Tasks::Data::Tree
|
|
161
146
|
|
162
147
|
private
|
163
148
|
|
149
|
+
def merge_node!(node)
|
150
|
+
if key_to_node.key?(node.key)
|
151
|
+
our = key_to_node[node.key]
|
152
|
+
return if our == node
|
153
|
+
our.value = node.value if node.leaf?
|
154
|
+
our.data.merge!(node.data) if node.data?
|
155
|
+
if node.children?
|
156
|
+
if our.children
|
157
|
+
our.children.merge!(node.children)
|
158
|
+
else
|
159
|
+
warn_add_children_to_leaf our
|
160
|
+
our.children = node.children
|
161
|
+
end
|
162
|
+
end
|
163
|
+
else
|
164
|
+
key_to_node[node.key] = node.derive(parent: parent)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
164
168
|
def warn_add_children_to_leaf(node)
|
165
169
|
::I18n::Tasks::Logging.log_warn "'#{node.full_key}' was a leaf, now has children (value <- scope conflict)"
|
166
170
|
end
|
167
171
|
|
168
172
|
class << self
|
169
|
-
include SplitKey
|
173
|
+
include ::I18n::Tasks::SplitKey
|
170
174
|
|
171
175
|
def null
|
172
176
|
new
|
@@ -21,7 +21,11 @@ module I18n::Tasks
|
|
21
21
|
nodes = to_nodes
|
22
22
|
unless nodes.empty?
|
23
23
|
block.yield nodes
|
24
|
-
|
24
|
+
if nodes.children.size == 1
|
25
|
+
first.children
|
26
|
+
else
|
27
|
+
Nodes.new(nodes: nodes.children)
|
28
|
+
end.levels(&block)
|
25
29
|
end
|
26
30
|
self
|
27
31
|
end
|
data/lib/i18n/tasks/logging.rb
CHANGED
data/lib/i18n/tasks/split_key.rb
CHANGED
@@ -1,65 +1,69 @@
|
|
1
|
-
module
|
2
|
-
|
1
|
+
module I18n
|
2
|
+
module Tasks
|
3
|
+
module SplitKey
|
4
|
+
extend self
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
6
|
+
# split a key by dots (.)
|
7
|
+
# dots inside braces or parenthesis are not split on
|
8
|
+
#
|
9
|
+
# split_key 'a.b' # => ['a', 'b']
|
10
|
+
# split_key 'a.#{b.c}' # => ['a', '#{b.c}']
|
11
|
+
# split_key 'a.b.c', 2 # => ['a', 'b.c']
|
12
|
+
def split_key(key, max = Float::INFINITY)
|
13
|
+
parts = []
|
14
|
+
pos = 0
|
15
|
+
return [key] if max == 1
|
16
|
+
key_parts(key) do |part|
|
17
|
+
parts << part
|
18
|
+
pos += part.length + 1
|
19
|
+
if parts.length + 1 >= max
|
20
|
+
parts << key.from(pos) unless pos == key.length
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
parts
|
20
25
|
end
|
21
|
-
end
|
22
|
-
parts
|
23
|
-
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def last_key_part(key)
|
28
|
+
last = nil
|
29
|
+
key_parts(key) { |part| last = part }
|
30
|
+
last
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
33
|
+
# yield each key part
|
34
|
+
# dots inside braces or parenthesis are not split on
|
35
|
+
def key_parts(key, &block)
|
36
|
+
return enum_for(:key_parts, key) unless block
|
37
|
+
nesting = PARENS
|
38
|
+
counts = PARENS_ZEROS # dup'd later if key contains parenthesis
|
39
|
+
delim = '.'.freeze
|
40
|
+
from = to = 0
|
41
|
+
key.each_char do |char|
|
42
|
+
if char == delim && PARENS_ZEROS == counts
|
43
|
+
block.yield key[from...to]
|
44
|
+
from = to = (to + 1)
|
45
|
+
else
|
46
|
+
nest_i, nest_inc = nesting[char]
|
47
|
+
if nest_i
|
48
|
+
counts = counts.dup if counts.frozen?
|
49
|
+
counts[nest_i] += nest_inc
|
50
|
+
end
|
51
|
+
to += 1
|
52
|
+
end
|
48
53
|
end
|
49
|
-
to
|
54
|
+
block.yield(key[from...to]) if from < to && to <= key.length
|
55
|
+
true
|
50
56
|
end
|
57
|
+
|
58
|
+
PARENS = %w({} [] ()).inject({}) { |h, s|
|
59
|
+
i = h.size / 2
|
60
|
+
h[s[0].freeze] = [i, 1].freeze
|
61
|
+
h[s[1].freeze] = [i, -1].freeze
|
62
|
+
h
|
63
|
+
}.freeze
|
64
|
+
PARENS_ZEROS = Array.new(PARENS.size, 0).freeze
|
65
|
+
private_constant :PARENS
|
66
|
+
private_constant :PARENS_ZEROS
|
51
67
|
end
|
52
|
-
block.yield(key[from...to]) if from < to && to <= key.length
|
53
|
-
true
|
54
68
|
end
|
55
|
-
|
56
|
-
PARENS = %w({} [] ()).inject({}) { |h, s|
|
57
|
-
i = h.size / 2
|
58
|
-
h[s[0].freeze] = [i, 1].freeze
|
59
|
-
h[s[1].freeze] = [i, -1].freeze
|
60
|
-
h
|
61
|
-
}.freeze
|
62
|
-
PARENS_ZEROS = Array.new(PARENS.size, 0).freeze
|
63
|
-
private_constant :PARENS
|
64
|
-
private_constant :PARENS_ZEROS
|
65
69
|
end
|
data/lib/i18n/tasks/version.rb
CHANGED
data/spec/i18n_tasks_spec.rb
CHANGED
@@ -5,6 +5,17 @@ require 'fileutils'
|
|
5
5
|
describe 'i18n-tasks' do
|
6
6
|
delegate :run_cmd, :i18n_task, :in_test_app_dir, to: :TestCodebase
|
7
7
|
|
8
|
+
describe 'health' do
|
9
|
+
it 'outputs stats' do
|
10
|
+
t = i18n_task
|
11
|
+
stats = in_test_app_dir { t.forest_stats(t.data_forest t.locales) }
|
12
|
+
out = capture_stderr { run_cmd :health }
|
13
|
+
stats.values.each do |v|
|
14
|
+
expect(out).to include(v.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
8
19
|
describe 'missing' do
|
9
20
|
let (:expected_missing_keys) {
|
10
21
|
%w( en.used_but_missing.key en.relative.index.missing
|
@@ -57,7 +68,7 @@ describe 'i18n-tasks' do
|
|
57
68
|
it 'removes unused' do
|
58
69
|
in_test_app_dir do
|
59
70
|
t = i18n_task
|
60
|
-
unused = expected_unused_keys.map { |k| SplitKey.split_key(k, 2)[1] }
|
71
|
+
unused = expected_unused_keys.map { |k| ::I18n::Tasks::SplitKey.split_key(k, 2)[1] }
|
61
72
|
unused.each do |key|
|
62
73
|
expect(t.key_value?(key, :en)).to be true
|
63
74
|
expect(t.key_value?(key, :es)).to be true
|
@@ -76,6 +87,21 @@ describe 'i18n-tasks' do
|
|
76
87
|
end
|
77
88
|
|
78
89
|
describe 'normalize' do
|
90
|
+
it 'sorts the keys' do
|
91
|
+
in_test_app_dir do
|
92
|
+
run_cmd :normalize
|
93
|
+
en_yml_data = i18n_task.data.reload['en'].select_keys { |_k, node|
|
94
|
+
node.data[:path] == 'config/locales/en.yml'
|
95
|
+
}
|
96
|
+
expect(en_yml_data).to be_present
|
97
|
+
en_yml_data.nodes { |nodes|
|
98
|
+
next unless nodes.children
|
99
|
+
keys = nodes.children.map(&:key)
|
100
|
+
expect(keys).to eq keys.sort
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
79
105
|
it 'moves keys to the corresponding files as per data.write' do
|
80
106
|
in_test_app_dir {
|
81
107
|
expect(File).to_not exist 'config/locales/devise.en.yml'
|
@@ -87,5 +87,10 @@ describe 'Tree siblings / forest' do
|
|
87
87
|
t['a.b.c.' + node.key] = node
|
88
88
|
expect(t['a.b.c.d'].value).to eq('e')
|
89
89
|
end
|
90
|
+
|
91
|
+
it '#inspect' do
|
92
|
+
expect(build_tree(a_hash).inspect).to eq "a: 1\nb\n ba: 1\n bb: 2"
|
93
|
+
expect(build_tree({}).inspect).to eq '{∅}'
|
94
|
+
end
|
90
95
|
end
|
91
96
|
end
|
data/spec/split_key_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: erubis
|
@@ -195,8 +195,9 @@ dependencies:
|
|
195
195
|
description: |
|
196
196
|
i18n-tasks helps you find and manage missing and unused translations.
|
197
197
|
|
198
|
-
It
|
199
|
-
|
198
|
+
It analyses code statically for key usages, such as `I18n.t('some.key')`, in order to report keys that are missing or unused,
|
199
|
+
pre-fill missing keys (optionally from Google Translate), and remove unused keys.
|
200
|
+
well.
|
200
201
|
email:
|
201
202
|
- glex.spb@gmail.com
|
202
203
|
executables:
|
@@ -319,7 +320,6 @@ post_install_message: |
|
|
319
320
|
cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
320
321
|
# Add an RSpec for missing and unused keys:
|
321
322
|
cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
322
|
-
}
|
323
323
|
rdoc_options: []
|
324
324
|
require_paths:
|
325
325
|
- lib
|