Skip to content

Commit 2d9e124

Browse files
authored
feat: use fallback locales from cldr (#820)
1 parent 22667ad commit 2d9e124

11 files changed

Lines changed: 397 additions & 81 deletions

File tree

docs/ref/conf.rst

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Default config:
2020
}],
2121
"compileNamespace": "cjs",
2222
"extractBabelOptions": {},
23-
"fallbackLocale": "",
23+
"fallbackLocales": {},
2424
"format": "po",
2525
"locales": [],
2626
"orderBy": "messageId",
@@ -230,17 +230,49 @@ extracted. This is required when project doesn't use standard Babel config
230230
}
231231
}
232232
233-
.. config:: fallbackLocale
233+
.. config:: fallbackLocales
234234

235-
fallbackLocale
235+
fallbackLocales
236236
--------------
237237

238-
Default: ``''``
238+
Default: ``{}``
239+
240+
:conf:`fallbackLocales` by default is using `CLDR Parent Locales <https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/parentLocales.json>`_, unless you disable it with a `false`:
241+
242+
.. code-block:: json
243+
244+
{
245+
"fallbackLocales": false
246+
}
247+
248+
:conf:`fallbackLocales` object let's us configure fallback locales to each locale instance.
249+
250+
.. code-block:: json
251+
252+
{
253+
"fallbackLocales": {
254+
"en-US": ["en-GB", "en"],
255+
"es-MX": "es"
256+
}
257+
}
258+
259+
On this example if any translation isn't found on `en-US` then will search on `en-GB`, after that if not found we'll search in `en`
260+
261+
Also, we can configure a default one for everything:
262+
263+
.. code-block:: json
264+
265+
{
266+
"fallbackLocales": {
267+
"en-US": ["en-GB", "en"],
268+
"es-MX": "es",
269+
"default": "en"
270+
}
271+
}
239272
240-
Translation from :conf:`fallbackLocale` is used when translation for given locale is missing.
273+
Translations from :conf:`fallbackLocales` is used when translation for given locale is missing.
241274

242-
If :conf:`fallbackLocale` isn't defined or translation in :conf:`fallbackLocale` is
243-
missing too, either default message or message ID is used instead.
275+
If :conf:`fallbackLocales` is `false` default message or message ID is used instead.
244276

245277
.. config:: format
246278

@@ -393,6 +425,6 @@ Catalog for :conf:`sourceLocale` doesn't require translated messages, because me
393425
IDs are used by default. However, it's still possible to override message ID by
394426
providing custom translation.
395427

396-
The difference between :conf:`fallbackLocale` and :conf:`sourceLocale` is that
397-
:conf:`fallbackLocale` is used in translation, while :conf:`sourceLocale` is
428+
The difference between :conf:`fallbackLocales` and :conf:`sourceLocale` is that
429+
:conf:`fallbackLocales` is used in translation, while :conf:`sourceLocale` is
398430
used for the message ID.

packages/cli/src/api/catalog.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import glob from "glob"
77
import micromatch from "micromatch"
88
import normalize from "normalize-path"
99

10-
import { LinguiConfig, OrderBy } from "@lingui/conf"
10+
import { LinguiConfig, OrderBy, FallbackLocales } from "@lingui/conf"
1111

1212
import getFormat from "./formats"
1313
import { CatalogFormatter } from "./formats/types"
@@ -46,7 +46,7 @@ export type MergeOptions = {
4646

4747
export type GetTranslationsOptions = {
4848
sourceLocale: string
49-
fallbackLocale: string
49+
fallbackLocales: FallbackLocales
5050
}
5151

5252
type CatalogProps = {
@@ -94,7 +94,7 @@ export class Catalog {
9494
)
9595
) as unknown) as (catalog: AllCatalogsType) => AllCatalogsType
9696

97-
const sortedCatalogs = cleanAndSort(catalogs);
97+
const sortedCatalogs = cleanAndSort(catalogs)
9898

9999
if (options.locale) {
100100
this.write(options.locale, sortedCatalogs[options.locale])
@@ -227,26 +227,45 @@ export class Catalog {
227227
catalogs: Object,
228228
locale: string,
229229
key: string,
230-
{ fallbackLocale, sourceLocale }: GetTranslationsOptions
230+
{ fallbackLocales, sourceLocale }: GetTranslationsOptions
231231
) {
232232
if (!catalogs[locale].hasOwnProperty(key)) {
233233
console.error(`Message with key ${key} is missing in locale ${locale}`)
234234
}
235235

236236
const getTranslation = (locale) => catalogs[locale][key].translation
237237

238+
const getMultipleFallbacks = (locale) => {
239+
const fL = fallbackLocales[locale]
240+
241+
// some probably the fallback will be undefined, so just search by locale
242+
if (!fL) return null
243+
244+
if (Array.isArray(fL)) {
245+
for (const fallbackLocale of fL) {
246+
if (catalogs[fallbackLocale]) {
247+
return getTranslation(fallbackLocale)
248+
}
249+
}
250+
} else {
251+
return getTranslation(fL)
252+
}
253+
}
254+
238255
return (
239256
// Get translation in target locale
240257
getTranslation(locale) ||
241-
// Get translation in fallbackLocale (if any)
242-
(fallbackLocale && getTranslation(fallbackLocale)) ||
258+
// We search in fallbackLocales as dependent of each locale
259+
getMultipleFallbacks(locale) ||
260+
// Get translation in fallbackLocales.default (if any)
261+
(fallbackLocales.default && getTranslation(fallbackLocales.default)) ||
243262
// Get message default
244263
catalogs[locale][key].defaults ||
245264
// If sourceLocale is either target locale of fallback one, use key
246265
(sourceLocale && sourceLocale === locale && key) ||
247266
(sourceLocale &&
248-
fallbackLocale &&
249-
sourceLocale === fallbackLocale &&
267+
fallbackLocales.default &&
268+
sourceLocale === fallbackLocales.default &&
250269
key) ||
251270
// Otherwise no translation is available
252271
undefined

packages/cli/src/api/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,16 @@ export type AllCatalogsType = {
3636
[locale: string]: CatalogType
3737
}
3838

39+
export type LocaleObject = {
40+
[locale: string]: string[] | string
41+
}
42+
export type DefaultLocaleObject = {
43+
default: string
44+
}
45+
export declare type FallbackLocales = LocaleObject | DefaultLocaleObject | false
46+
3947
export type getTranslationOptions = {
40-
fallbackLocale: string
48+
fallbackLocales: FallbackLocales
4149
sourceLocale: string
4250
}
4351

packages/cli/src/lingui-compile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function command(config, options) {
5454
const messages = catalog.getTranslations(
5555
locale === config.pseudoLocale ? config.sourceLocale : locale,
5656
{
57-
fallbackLocale: config.fallbackLocale,
57+
fallbackLocales: config.fallbackLocales,
5858
sourceLocale: config.sourceLocale,
5959
}
6060
)

packages/conf/index.d.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,22 @@ declare type CatalogConfig = {
99
include: string[];
1010
exclude?: string[];
1111
};
12+
13+
export type LocaleObject = {
14+
[locale: string]: string[] | string
15+
}
16+
17+
export type DefaultLocaleObject = {
18+
default: string
19+
}
20+
21+
export declare type FallbackLocales = LocaleObject | DefaultLocaleObject
22+
1223
export declare type LinguiConfig = {
1324
catalogs: CatalogConfig[];
1425
compileNamespace: string;
1526
extractBabelOptions: Object;
16-
fallbackLocale: string;
27+
fallbackLocales: FallbackLocales;
1728
format: CatalogFormat;
1829
prevFormat: CatalogFormat;
1930
formatOptions: CatalogFormatOptions;
@@ -42,7 +53,7 @@ export declare const configValidation: {
4253
};
4354
catalogs: CatalogConfig[];
4455
compileNamespace: string;
45-
fallbackLocale: string;
56+
fallbackLocales: FallbackLocales;
4657
format: CatalogFormat;
4758
formatOptions: CatalogFormatOptions;
4859
locales: string[];
@@ -53,7 +64,7 @@ export declare const configValidation: {
5364
sourceLocale: string;
5465
};
5566
deprecatedConfig: {
56-
fallbackLanguage: (config: LinguiConfig & DeprecatedFallbackLanguage) => string;
67+
fallbackLocale: (config: LinguiConfig & DeprecatedFallbackLanguage) => string;
5768
localeDir: (config: LinguiConfig & DeprecatedLocaleDir) => string;
5869
srcPathDirs: (config: LinguiConfig & DeprecatedLocaleDir) => string;
5970
srcPathIgnorePatterns: (config: LinguiConfig & DeprecatedLocaleDir) => string;
@@ -62,14 +73,15 @@ export declare const configValidation: {
6273
};
6374
export declare function replaceRootDir(config: LinguiConfig, rootDir: string): LinguiConfig;
6475
/**
65-
* Replace fallbackLanguage with fallbackLocale
76+
* Replace fallbackLocale with fallbackLocales
6677
*
6778
* Released in lingui-conf 0.9
68-
* Remove anytime after 3.x
79+
* Remove anytime after 4.x
6980
*/
7081
declare type DeprecatedFallbackLanguage = {
71-
fallbackLanguage: string | null;
82+
fallbackLocale: string | null;
7283
};
84+
7385
export declare function fallbackLanguageMigration(config: LinguiConfig & DeprecatedFallbackLanguage): LinguiConfig;
7486
/**
7587
* Replace localeDir, srcPathDirs and srcPathIgnorePatterns with catalogs

packages/conf/src/__snapshots__/index.test.ts.snap

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ Documentation: https://lingui.js.org/ref/conf.html
7272
Please update your configuration.
7373
7474
75+
Documentation: https://lingui.js.org/ref/conf.html
76+
`;
77+
78+
exports[`@lingui/conf fallbackLocales logic if fallbackLocale is defined, we use the default one on fallbackLocales 1`] = `
79+
● Deprecation Warning:
80+
81+
Option fallbackLocale was replaced by fallbackLocales
82+
83+
You can find more information here: https://github.com/lingui/js-lingui/issues/791
84+
85+
@lingui/cli now treats your current configuration as:
86+
{
87+
"fallbackLocales": {
88+
default: "en"
89+
}
90+
}
91+
92+
Please update your configuration.
93+
94+
7595
Documentation: https://lingui.js.org/ref/conf.html
7696
`;
7797

@@ -93,7 +113,9 @@ Object {
93113
plugins: Array [],
94114
presets: Array [],
95115
},
96-
fallbackLocale: ,
116+
fallbackLocales: Object {
117+
en-gb: en,
118+
},
97119
format: po,
98120
formatOptions: Object {
99121
origins: true,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
locales: ["en-US", "es-MX"],
3+
fallbackLocales: {
4+
"en-US": ["en"],
5+
"default": "en"
6+
}
7+
}

packages/conf/src/index.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from "path"
2+
import mockFs from "mock-fs"
23
import { validate } from "jest-validate"
34
import {
45
getConfig,
@@ -142,4 +143,40 @@ describe("@lingui/conf", function () {
142143
})
143144
})
144145
})
146+
147+
describe("fallbackLocales logic", () => {
148+
afterEach(() => {
149+
mockFs.restore()
150+
})
151+
152+
it ("if fallbackLocale is defined, we use the default one on fallbackLocales", () => {
153+
mockFs({
154+
".linguirc": JSON.stringify({
155+
locales: ["en-US"],
156+
fallbackLocale: "en"
157+
})
158+
})
159+
mockConsole((console) => {
160+
const config = getConfig({
161+
configPath: ".linguirc",
162+
})
163+
expect(config.fallbackLocales.default).toEqual("en")
164+
expect(getConsoleMockCalls(console.warn)).toMatchSnapshot()
165+
})
166+
})
167+
168+
it ("if fallbackLocales is defined, we also build the cldr", () => {
169+
const config = getConfig({
170+
configPath: path.resolve(
171+
__dirname,
172+
path.join("fixtures", "valid", ".fallbacklocalesrc")
173+
),
174+
})
175+
expect(config.fallbackLocales).toEqual({
176+
"en-US": "en",
177+
default: "en",
178+
"es-MX": "es"
179+
})
180+
})
181+
})
145182
})

0 commit comments

Comments
 (0)