Skip to content

Commit d3f6733

Browse files
committed
Respect I18n.fallbacks chain in embed_fallback_translations plugin.
1 parent 7bd9a0e commit d3f6733

16 files changed

Lines changed: 190 additions & 15 deletions

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,15 @@ both `--config` and `--require`.
153153

154154
##### `embed_fallback_translations`:
155155

156-
Embed fallback translations inferred from the default locale. This can be useful
157-
in cases where you have multiple large translation files and don't want to load
158-
the default locale together with the target locale.
156+
Embed fallback translations into each locale. This can be useful in cases where
157+
you have multiple large translation files and don't want to load the default
158+
locale together with the target locale.
159+
160+
When `I18n.fallbacks` is configured (e.g. via `I18n::Backend::Fallbacks` or
161+
Rails' `config.i18n.fallbacks`), the full fallback chain is respected. For
162+
example, if `es` falls back to `pt` which falls back to `en`, missing `es`
163+
translations are first filled from `pt`, then from `en`. When `I18n.fallbacks`
164+
is not configured, translations fall back directly to `I18n.default_locale`.
159165

160166
To use it, add the following to your configuration file:
161167

@@ -166,6 +172,18 @@ pipeline:
166172
enabled: true
167173
```
168174
175+
> [!NOTE]
176+
>
177+
> `I18n.fallbacks=(*)` is weird. The most common way of using it is with
178+
> `I18n.fallbacks[:es] = [:pt, :en]` and
179+
> `I18n.fallbacks = I18n::Locale::Fallbacks(default_locale, hash_map)`. If you
180+
> assign just a hash, no error will be raised, but it's not the correct
181+
> behavior, and you'll get a hash back, rather than a `I18n::Locale::Fallbacks`
182+
> instance.
183+
>
184+
> When assigning an array, remember to pass the default locale as the last
185+
> argument, as precedence is from left to right.
186+
169187
##### `export_files`:
170188

171189
By default, i18n-js will export only JSON files out of your translations. This
@@ -224,7 +242,7 @@ i18n.store({
224242
es: {
225243
"bunny rabbit adventure": "conejito conejo aventura",
226244
bye: "adios",
227-
"time for bed!": "hora de acostarse!",
245+
"time for bed!": "¡hora de acostarse!",
228246
},
229247
pt: {
230248
"bunny rabbit adventure": "a aventura da coelhinha",

lib/i18n-js/embed_fallback_translations_plugin.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ def validate_schema
1414
def transform(translations:)
1515
return translations unless enabled?
1616

17+
if use_fallback?
18+
transform_with_i18n_fallbacks(translations)
19+
else
20+
transform_with_default_locale(translations)
21+
end
22+
end
23+
24+
private def use_fallback?
25+
I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) &&
26+
I18n.respond_to?(:fallbacks) &&
27+
I18n.fallbacks.defaults.any?
28+
end
29+
30+
private def transform_with_default_locale(translations)
1731
fallback_locale = I18n.default_locale.to_sym
1832
locales_to_fallback = translations.keys - [fallback_locale]
1933

@@ -29,6 +43,30 @@ def transform(translations:)
2943

3044
translations_with_fallback
3145
end
46+
47+
private def transform_with_i18n_fallbacks(translations)
48+
translations_with_fallback = {}
49+
50+
translations.each do |locale, result|
51+
if locale == I18n.default_locale
52+
translations_with_fallback[locale] = result
53+
next
54+
end
55+
56+
fallback_locales =
57+
I18n.fallbacks[locale].map(&:to_sym) - [locale.to_sym]
58+
59+
fallback_locales.each do |fallback_locale|
60+
next unless translations[fallback_locale]
61+
62+
result = I18nJS.deep_merge(translations[fallback_locale], result)
63+
end
64+
65+
translations_with_fallback[locale] = result
66+
end
67+
68+
translations_with_fallback
69+
end
3270
end
3371

3472
I18nJS.register_plugin(EmbedFallbackTranslationsPlugin)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
translations:
3+
- file: test/output/embed_fallback_translations_with_i18n_fallbacks.json
4+
patterns:
5+
- "*"
6+
7+
pipeline:
8+
- plugin: embed_fallback_translations
9+
enabled: true

test/fixtures/expected/embed_fallback_translations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"one": "This is one!"
1818
},
1919
"hello_sunshine": "hello sunshine!",
20-
"time_for_bed": "hora de acostarse!",
20+
"time_for_bed": "¡hora de acostarse!",
2121
"words": {
2222
"bye": "Goodbye!",
2323
"hello": "Hi!"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"en": {
3+
"bunny_rabbit_adventure": "bunny rabbit adventure",
4+
"counting": {
5+
"one": "This is one!"
6+
},
7+
"hello_sunshine": "hello sunshine!",
8+
"time_for_bed": "time for bed!",
9+
"words": {
10+
"bye": "Goodbye!",
11+
"hello": "Hi!"
12+
}
13+
},
14+
"es": {
15+
"bunny_rabbit_adventure": "conejito conejo aventura",
16+
"bye": "adios",
17+
"counting": {
18+
"one": "Esse é um!"
19+
},
20+
"hello_sunshine": "hello sunshine!",
21+
"time_for_bed": "¡hora de acostarse!",
22+
"words": {
23+
"bye": "Adeus!",
24+
"hello": "Hi!"
25+
}
26+
},
27+
"pt": {
28+
"bunny_rabbit_adventure": "a aventura da coelhinha",
29+
"bye": "tchau",
30+
"counting": {
31+
"one": "Esse é um!"
32+
},
33+
"hello_sunshine": "hello sunshine!",
34+
"time_for_bed": "hora de dormir!",
35+
"words": {
36+
"bye": "Adeus!",
37+
"hello": "Hi!"
38+
}
39+
}
40+
}

test/fixtures/expected/everything.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"es": {
88
"bunny_rabbit_adventure": "conejito conejo aventura",
99
"bye": "adios",
10-
"time_for_bed": "hora de acostarse!"
10+
"time_for_bed": "¡hora de acostarse!"
1111
},
1212
"pt": {
1313
"bunny_rabbit_adventure": "a aventura da coelhinha",

test/fixtures/expected/export_files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ i18n.store({
1111
"es": {
1212
"bunny_rabbit_adventure": "conejito conejo aventura",
1313
"bye": "adios",
14-
"time_for_bed": "hora de acostarse!"
14+
"time_for_bed": "¡hora de acostarse!"
1515
},
1616
"pt": {
1717
"bunny_rabbit_adventure": "a aventura da coelhinha",

test/fixtures/expected/multiple_files/es.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"es": {
33
"bunny_rabbit_adventure": "conejito conejo aventura",
44
"bye": "adios",
5-
"time_for_bed": "hora de acostarse!"
5+
"time_for_bed": "¡hora de acostarse!"
66
}
77
}

test/fixtures/expected/specific.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
},
55
"es": {
66
"bye": "adios",
7-
"time_for_bed": "hora de acostarse!"
7+
"time_for_bed": "¡hora de acostarse!"
88
},
99
"pt": {
1010
"bye": "tchau",

test/fixtures/expected/transformed.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"es": {
99
"bunny_rabbit_adventure": "conejito conejo aventura",
1010
"bye": "adios",
11-
"time_for_bed": "hora de acostarse!",
11+
"time_for_bed": "¡hora de acostarse!",
1212
"injected": "yes:es"
1313
},
1414
"pt": {

0 commit comments

Comments
 (0)