Skip to content

Commit 7f2d329

Browse files
authored
Fix fallback locale (#103)
* complete fallback locale * refactor + fallbackMissingTranslations option * refactor and update tests
1 parent ab7561a commit 7f2d329

File tree

6 files changed

+72
-5
lines changed

6 files changed

+72
-5
lines changed

src/index.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let sharedInstance: I18n = null
2121
const DEFAULT_OPTIONS: OptionsInterface = {
2222
lang: !isServer && document.documentElement.lang ? document.documentElement.lang.replace('-', '_') : null,
2323
fallbackLang: 'en',
24+
fallbackMissingTranslations: false,
2425
resolve: (lang: string) => new Promise((resolve) => resolve({ default: {} })),
2526
onLoad: (lang: string) => {}
2627
}
@@ -129,6 +130,9 @@ export class I18n {
129130
// Stores messages for the currently active language
130131
private activeMessages: object = reactive({})
131132

133+
// Stores messages for fallback language
134+
private fallbackMessages: object = reactive({})
135+
132136
// Stores the abort controller for the load promises.
133137
private abortController: AbortController
134138

@@ -137,8 +141,11 @@ export class I18n {
137141
*/
138142
constructor(options: OptionsInterface = {}) {
139143
this.options = { ...DEFAULT_OPTIONS, ...options }
140-
141-
this.load()
144+
if (this.options.fallbackMissingTranslations) {
145+
this.loadFallbackLanguage()
146+
} else {
147+
this.load()
148+
}
142149
}
143150

144151
/**
@@ -161,6 +168,32 @@ export class I18n {
161168
this[isServer ? 'loadLanguage' : 'loadLanguageAsync'](this.getActiveLanguage())
162169
}
163170

171+
/**
172+
* Load fallback language
173+
*/
174+
loadFallbackLanguage(): void {
175+
if (!isServer) {
176+
this.resolveLangAsync(this.options.resolve, this.options.fallbackLang).then(({ default: messages }) => {
177+
for (const [key, value] of Object.entries(messages)) {
178+
this.fallbackMessages[key] = value
179+
}
180+
const lang = this.options.fallbackLang
181+
const data: LanguageInterface = { lang, messages }
182+
I18n.loaded.push(data)
183+
this.load()
184+
})
185+
} else {
186+
const { default: messages } = this.resolveLang(this.options.resolve, this.options.fallbackLang)
187+
for (const [key, value] of Object.entries(messages)) {
188+
this.fallbackMessages[key] = value
189+
}
190+
const lang = this.options.fallbackLang
191+
const data: LanguageInterface = { lang, messages }
192+
I18n.loaded.push(data)
193+
this.loadLanguage(this.getActiveLanguage())
194+
}
195+
}
196+
164197
/**
165198
* Loads the language async.
166199
*/
@@ -300,8 +333,14 @@ export class I18n {
300333
this.activeMessages[key] = value
301334
}
302335

336+
for (const [key, value] of Object.entries(this.fallbackMessages)) {
337+
if (!this.activeMessages[key] || this.activeMessages[key] === key) {
338+
this.activeMessages[key] = value
339+
}
340+
}
341+
303342
for (const [key] of Object.entries(this.activeMessages)) {
304-
if (!messages[key]) {
343+
if (!messages[key] && !this.fallbackMessages[key]) {
305344
this.activeMessages[key] = null
306345
}
307346
}

src/interfaces/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { LanguageJsonFileInterface } from './language-json-file'
66
export interface OptionsInterface {
77
lang?: string
88
fallbackLang?: string
9+
fallbackMissingTranslations?: boolean
910
resolve?(lang: string): Promise<LanguageJsonFileInterface>
1011
onLoad?: (lang: string) => void
1112
}

test/class.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,21 @@ it('calls onLoad when loaded', async () => {
8484
expect(onLoadFunction).toHaveBeenCalledWith('en')
8585
expect(onLoadFunction).toHaveBeenCalledWith('pt')
8686
})
87+
88+
it('can override missing translations with fallback language translations', async () => {
89+
const onLoadFunction = jest.fn()
90+
const i18n = new I18n({
91+
fallbackLang: 'en',
92+
fallbackMissingTranslations: true,
93+
resolve: lang => import(`./fixtures/lang/${lang}.json`),
94+
onLoad: onLoadFunction
95+
})
96+
await i18n.loadLanguageAsync('pt')
97+
98+
expect(onLoadFunction).toHaveBeenCalledTimes(1)
99+
100+
expect(i18n.getActiveLanguage()).toBe('pt')
101+
expect(i18n.trans('Welcome!')).toBe('Bem-vindo!')
102+
103+
expect(i18n.trans('English only.')).toBe('English only.')
104+
})

test/fixtures/lang/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"Only Available on EN": "Only Available on EN",
55
"{1} :count minute ago|[2,*] :count minutes ago": "{1} :count minute ago|[2,*] :count minutes ago",
66
"Start/end": "Start/End",
7-
"Get started.": "Get started."
7+
"Get started.": "Get started.",
8+
"English only.": "English only."
89
}

test/setup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { mount } from '@vue/test-utils'
22
import { i18nVue } from '../src'
33
import { parseAll } from '../src/loader'
44

5-
global.mountPlugin = async (template = '<div />', lang = 'pt', fallbackLang = 'pt') => {
5+
global.mountPlugin = async (template = '<div />', lang = 'pt', fallbackLang = 'pt', fallbackMissingTranslations = false) => {
66
const wrapper = mount({ template }, {
77
global: {
88
plugins: [[i18nVue, {
99
lang,
1010
fallbackLang,
11+
fallbackMissingTranslations,
1112
resolve: lang => import(`./fixtures/lang/${lang}.json`),
1213
}]]
1314
}

test/translate.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ it('fallback to the `fallbackLang` if the `lang` was not found', async () => {
4747
expect(trans('Welcome!')).toBe('Bem-vindo!');
4848
});
4949

50+
it('fallback individual translation entries to the `fallbackLang` if translation was not found in the active language', async () => {
51+
await global.mountPlugin(`<div />`, 'pt', 'en', 'true');
52+
53+
expect(trans('Welcome!')).toBe('Bem-vindo!');
54+
expect(trans('English only.')).toBe('English only.');
55+
});
56+
5057
it('returns the given key if the key is not available on the lang', async () => {
5158
await global.mountPlugin(`<div />`, 'en');
5259
expect(trans('Only Available on EN')).toBe('Only Available on EN');

0 commit comments

Comments
 (0)