Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fallback cldr #820

Merged
merged 10 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions docs/ref/conf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Default config:
}],
"compileNamespace": "cjs",
"extractBabelOptions": {},
"fallbackLocale": "",
"fallbackLocales": {},
"format": "po",
"locales": [],
"orderBy": "messageId",
Expand Down Expand Up @@ -230,17 +230,49 @@ extracted. This is required when project doesn't use standard Babel config
}
}

.. config:: fallbackLocale
.. config:: fallbackLocales

fallbackLocale
fallbackLocales
--------------

Default: ``''``
Default: ``{}``

: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`:

.. code-block:: json

{
"fallbackLocales": false
}

:conf:`fallbackLocales` object let's us configure fallback locales to each locale instance.

.. code-block:: json

{
"fallbackLocales": {
"en-US": ["en-GB", "en"],
"es-MX": "es"
}
}

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`

Also, we can configure a default one for everything:

.. code-block:: json

{
"fallbackLocales": {
"en-US": ["en-GB", "en"],
"es-MX": "es",
"default": "en"
}
}

Translation from :conf:`fallbackLocale` is used when translation for given locale is missing.
Translations from :conf:`fallbackLocales` is used when translation for given locale is missing.

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

.. config:: format

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

The difference between :conf:`fallbackLocale` and :conf:`sourceLocale` is that
:conf:`fallbackLocale` is used in translation, while :conf:`sourceLocale` is
The difference between :conf:`fallbackLocales` and :conf:`sourceLocale` is that
:conf:`fallbackLocales` is used in translation, while :conf:`sourceLocale` is
used for the message ID.
35 changes: 27 additions & 8 deletions packages/cli/src/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import glob from "glob"
import micromatch from "micromatch"
import normalize from "normalize-path"

import { LinguiConfig, OrderBy } from "@lingui/conf"
import { LinguiConfig, OrderBy, FallbackLocales } from "@lingui/conf"

import getFormat from "./formats"
import { CatalogFormatter } from "./formats/types"
Expand Down Expand Up @@ -46,7 +46,7 @@ export type MergeOptions = {

export type GetTranslationsOptions = {
sourceLocale: string
fallbackLocale: string
fallbackLocales: FallbackLocales
}

type CatalogProps = {
Expand Down Expand Up @@ -94,7 +94,7 @@ export class Catalog {
)
) as unknown) as (catalog: AllCatalogsType) => AllCatalogsType

const sortedCatalogs = cleanAndSort(catalogs);
const sortedCatalogs = cleanAndSort(catalogs)

if (options.locale) {
this.write(options.locale, sortedCatalogs[options.locale])
Expand Down Expand Up @@ -227,26 +227,45 @@ export class Catalog {
catalogs: Object,
locale: string,
key: string,
{ fallbackLocale, sourceLocale }: GetTranslationsOptions
{ fallbackLocales, sourceLocale }: GetTranslationsOptions
) {
if (!catalogs[locale].hasOwnProperty(key)) {
console.error(`Message with key ${key} is missing in locale ${locale}`)
}

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

const getMultipleFallbacks = (locale) => {
const fL = fallbackLocales[locale]

// some probably the fallback will be undefined, so just search by locale
if (!fL) return null

if (Array.isArray(fL)) {
for (const fallbackLocale of fL) {
if (catalogs[fallbackLocale]) {
return getTranslation(fallbackLocale)
}
}
} else {
return getTranslation(fL)
}
}

return (
// Get translation in target locale
getTranslation(locale) ||
// Get translation in fallbackLocale (if any)
(fallbackLocale && getTranslation(fallbackLocale)) ||
// We search in fallbackLocales as dependent of each locale
getMultipleFallbacks(locale) ||
// Get translation in fallbackLocales.default (if any)
(fallbackLocales.default && getTranslation(fallbackLocales.default)) ||
// Get message default
catalogs[locale][key].defaults ||
// If sourceLocale is either target locale of fallback one, use key
(sourceLocale && sourceLocale === locale && key) ||
(sourceLocale &&
fallbackLocale &&
sourceLocale === fallbackLocale &&
fallbackLocales.default &&
sourceLocale === fallbackLocales.default &&
key) ||
// Otherwise no translation is available
undefined
Expand Down
10 changes: 9 additions & 1 deletion packages/cli/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ export type AllCatalogsType = {
[locale: string]: CatalogType
}

export type LocaleObject = {
[locale: string]: string[] | string
}
export type DefaultLocaleObject = {
default: string
}
export declare type FallbackLocales = LocaleObject | DefaultLocaleObject | false

export type getTranslationOptions = {
fallbackLocale: string
fallbackLocales: FallbackLocales
sourceLocale: string
}

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lingui-compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function command(config, options) {
const messages = catalog.getTranslations(
locale === config.pseudoLocale ? config.sourceLocale : locale,
{
fallbackLocale: config.fallbackLocale,
fallbackLocales: config.fallbackLocales,
sourceLocale: config.sourceLocale,
}
)
Expand Down
24 changes: 18 additions & 6 deletions packages/conf/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ declare type CatalogConfig = {
include: string[];
exclude?: string[];
};

export type LocaleObject = {
[locale: string]: string[] | string
}

export type DefaultLocaleObject = {
default: string
}

export declare type FallbackLocales = LocaleObject | DefaultLocaleObject

export declare type LinguiConfig = {
catalogs: CatalogConfig[];
compileNamespace: string;
extractBabelOptions: Object;
fallbackLocale: string;
fallbackLocales: FallbackLocales;
format: CatalogFormat;
prevFormat: CatalogFormat;
formatOptions: CatalogFormatOptions;
Expand Down Expand Up @@ -42,7 +53,7 @@ export declare const configValidation: {
};
catalogs: CatalogConfig[];
compileNamespace: string;
fallbackLocale: string;
fallbackLocales: FallbackLocales;
format: CatalogFormat;
formatOptions: CatalogFormatOptions;
locales: string[];
Expand All @@ -53,7 +64,7 @@ export declare const configValidation: {
sourceLocale: string;
};
deprecatedConfig: {
fallbackLanguage: (config: LinguiConfig & DeprecatedFallbackLanguage) => string;
fallbackLocale: (config: LinguiConfig & DeprecatedFallbackLanguage) => string;
localeDir: (config: LinguiConfig & DeprecatedLocaleDir) => string;
srcPathDirs: (config: LinguiConfig & DeprecatedLocaleDir) => string;
srcPathIgnorePatterns: (config: LinguiConfig & DeprecatedLocaleDir) => string;
Expand All @@ -62,14 +73,15 @@ export declare const configValidation: {
};
export declare function replaceRootDir(config: LinguiConfig, rootDir: string): LinguiConfig;
/**
* Replace fallbackLanguage with fallbackLocale
* Replace fallbackLocale with fallbackLocales
*
* Released in lingui-conf 0.9
* Remove anytime after 3.x
* Remove anytime after 4.x
*/
declare type DeprecatedFallbackLanguage = {
fallbackLanguage: string | null;
fallbackLocale: string | null;
};

export declare function fallbackLanguageMigration(config: LinguiConfig & DeprecatedFallbackLanguage): LinguiConfig;
/**
* Replace localeDir, srcPathDirs and srcPathIgnorePatterns with catalogs
Expand Down
24 changes: 23 additions & 1 deletion packages/conf/src/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ Documentation: https://lingui.js.org/ref/conf.html
Please update your configuration.


Documentation: https://lingui.js.org/ref/conf.html
`;

exports[`@lingui/conf fallbackLocales logic if fallbackLocale is defined, we use the default one on fallbackLocales 1`] = `
● Deprecation Warning:

Option fallbackLocale was replaced by fallbackLocales

You can find more information here: https://github.com/lingui/js-lingui/issues/791

@lingui/cli now treats your current configuration as:
{
"fallbackLocales": {
default: "en"
}
}

Please update your configuration.


Documentation: https://lingui.js.org/ref/conf.html
`;

Expand All @@ -93,7 +113,9 @@ Object {
plugins: Array [],
presets: Array [],
},
fallbackLocale: ,
fallbackLocales: Object {
en-gb: en,
},
format: po,
formatOptions: Object {
origins: true,
Expand Down
7 changes: 7 additions & 0 deletions packages/conf/src/fixtures/valid/.fallbacklocalesrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
locales: ["en-US", "es-MX"],
fallbackLocales: {
"en-US": ["en"],
"default": "en"
}
}
37 changes: 37 additions & 0 deletions packages/conf/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "path"
import mockFs from "mock-fs"
import { validate } from "jest-validate"
import {
getConfig,
Expand Down Expand Up @@ -142,4 +143,40 @@ describe("@lingui/conf", function () {
})
})
})

describe("fallbackLocales logic", () => {
afterEach(() => {
mockFs.restore()
})

it ("if fallbackLocale is defined, we use the default one on fallbackLocales", () => {
mockFs({
".linguirc": JSON.stringify({
locales: ["en-US"],
fallbackLocale: "en"
})
})
mockConsole((console) => {
const config = getConfig({
configPath: ".linguirc",
})
expect(config.fallbackLocales.default).toEqual("en")
expect(getConsoleMockCalls(console.warn)).toMatchSnapshot()
})
})

it ("if fallbackLocales is defined, we also build the cldr", () => {
const config = getConfig({
configPath: path.resolve(
__dirname,
path.join("fixtures", "valid", ".fallbacklocalesrc")
),
})
expect(config.fallbackLocales).toEqual({
"en-US": "en",
default: "en",
"es-MX": "es"
})
})
})
})
Loading