i18n-tasks 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|