Skip to content

Commit

Permalink
Transform nested translations into flat translations during assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinkasper authored and kkasperconsolinno committed Nov 28, 2023
1 parent fad0768 commit 76a4d1f
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 27 deletions.
3 changes: 1 addition & 2 deletions src/plugins/piral-translate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@
},
"dependencies": {
"deepmerge": "^4.2.2",
"get-value": "^3.0.1"
"flat": "^6.0.1"
},
"devDependencies": {
"@types/deepmerge": "^2.2.0",
"@types/get-value": "^3.0.5",
"@types/react": "^18.0.0",
"piral-core": "^1.3.3",
"react": "^18.0.0"
Expand Down
16 changes: 16 additions & 0 deletions src/plugins/piral-translate/src/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ describe('Create Localize API', () => {
expect(result).toEqual('bár');
});

it('createApi can translate from local-global translations using the current language and passed nested translations', () => {
const config = {
language: 'en'
};
const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
api.setTranslations({
en: {
header: {
title: 'Hello world'
}
}
});
const result = api.translate('header.title');
expect(result).toBe('Hello world');
});

it('createApi falls back to standard string if language is not found', () => {
const config = {
language: 'en',
Expand Down
11 changes: 6 additions & 5 deletions src/plugins/piral-translate/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { PiralPlugin } from 'piral-core';
import { createActions } from './actions';
import { Localizer } from './localize';
import { DefaultPicker } from './default';
import { PiletLocaleApi, LocalizationMessages, Localizable, PiralSelectLanguageEvent } from './types';
import { PiletLocaleApi, LocalizationMessages, Localizable, PiralSelectLanguageEvent, NestedLocalizationMessages } from './types';
import { flattenTranslations } from './flatten-translations';

export interface TranslationFallback {
(key: string, language: string): string;
Expand All @@ -22,7 +23,7 @@ export interface LocaleConfig {
* Sets the default (global) localization messages.
* @default {}
*/
messages?: LocalizationMessages;
messages?: LocalizationMessages | NestedLocalizationMessages;
/**
* Sets the default language to use.
*/
Expand Down Expand Up @@ -72,8 +73,8 @@ export function createLocaleApi(localizer: Localizable = setupLocalizer()): Pira
let localTranslations: LocalizationMessages = {};

return {
addTranslations(messages: LocalizationMessages[], isOverriding: boolean = true) {
const messagesToMerge: LocalizationMessages[] = messages;
addTranslations(messages, isOverriding = true) {
const messagesToMerge = messages;

if (isOverriding) {
messagesToMerge.unshift(localizer.messages);
Expand All @@ -100,7 +101,7 @@ export function createLocaleApi(localizer: Localizable = setupLocalizer()): Pira
return selected;
},
setTranslations(messages) {
localTranslations = messages;
localTranslations = flattenTranslations(messages);
},
getTranslations() {
return localTranslations;
Expand Down
42 changes: 42 additions & 0 deletions src/plugins/piral-translate/src/flatten-translations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @vitest-environment jsdom
*/
import { describe, it, expect } from 'vitest';
import { flattenTranslations } from './flatten-translations';

describe('Flatten translations', () => {
it('flattenTranslations can handle flat keys', () => {
const messages = {
en: {
key: 'value'
},
fr: {
key: 'value (fr)'
}
};
const flatMessages = flattenTranslations(messages);
expect(flatMessages.fr.key).toEqual('value (fr)');
});

it('flattenTranslations can handle flat dot keys', () => {
const messages = {
en: {
'header.title': 'Hello world'
}
};
const flatMessages = flattenTranslations(messages);
expect(flatMessages.en['header.title']).toBe('Hello world');
});

it('flattenTranslations can handle nested keys', () => {
const messages = {
en: {
header: {
title: 'Hello world'
}
}
};
const flatMessages = flattenTranslations(messages);
expect(flatMessages.en['header.title']).toBe('Hello world');
});
});
15 changes: 15 additions & 0 deletions src/plugins/piral-translate/src/flatten-translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { flatten } from 'flat';
import { LocalizationMessages, NestedLocalizationMessages } from './types';

export function flattenTranslations(messages: LocalizationMessages | NestedLocalizationMessages): LocalizationMessages {
return Object.fromEntries(
Object
.entries(messages)
.map(([ language, translations ]) => {
return [
language,
flatten(translations)
]
})
);
}
2 changes: 1 addition & 1 deletion src/plugins/piral-translate/src/localize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('Localize Module', () => {
expect(result).toBe('Hi , welcome back');
});

it('localizeGlobal uses nested translations if the key contains dots', () => {
it('localizeGlobal translates from global translations using passed nested translations', () => {
const localizer = new Localizer(messages, 'en');
const result = localizer.localizeGlobal('header.title');

Expand Down
14 changes: 9 additions & 5 deletions src/plugins/piral-translate/src/localize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import getValue from 'get-value';
import { LocalizationMessages, Localizable } from './types';
import { LocalizationMessages, Localizable, NestedLocalizationMessages } from './types';
import { flattenTranslations } from './flatten-translations';

function defaultFallback(key: string, language: string): string {
if (process.env.NODE_ENV === 'production') {
Expand All @@ -21,15 +21,19 @@ function formatMessage<T extends object>(message: string, variables: T): string
}

export class Localizer implements Localizable {
public messages: LocalizationMessages;

/**
* Creates a new instance of a localizer.
*/
constructor(
public messages: LocalizationMessages,
messages: LocalizationMessages | NestedLocalizationMessages,
public language: string,
public languages: Array<string>,
private fallback = defaultFallback,
) {}
) {
this.messages = flattenTranslations(messages);
}

/**
* Localizes the given key via the global translations.
Expand Down Expand Up @@ -70,7 +74,7 @@ export class Localizer implements Localizable {
private translateMessage<T extends object>(messages: LocalizationMessages, key: string, variables?: T) {
const language = this.language;
const translations = language && messages[language];
const translation = translations && getValue(translations, key);
const translation = translations && translations[key];
return translation && (variables ? formatMessage(translation, variables) : translation);
}
}
18 changes: 16 additions & 2 deletions src/plugins/piral-translate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ export interface Translations {
[tag: string]: string;
}

export interface NestedTranslations {
/**
* The available wordings (tag to translation or nested translations).
*/
[tag: string]: string | NestedTranslations;
}

export interface LanguageLoader {
(language: string, current: LanguageData): Promise<LanguageData>;
}
Expand All @@ -119,6 +126,13 @@ export interface LocalizationMessages {
[lang: string]: Translations;
}

export interface NestedLocalizationMessages {
/**
* The available languages (lang to wordings or nested wordings).
*/
[lang: string]: NestedTranslations;
}

export interface PiletLocaleApi {
/**
* Adds a list of translations to the existing translations.
Expand All @@ -129,7 +143,7 @@ export interface PiletLocaleApi {
* @param messagesList The list of messages that extend the existing translations
* @param [isOverriding=true] Indicates whether the new translations overwrite the existing translations
*/
addTranslations(messagesList: LocalizationMessages[], isOverriding?: boolean): void;
addTranslations(messagesList: (LocalizationMessages | NestedLocalizationMessages)[], isOverriding?: boolean): void;
/**
* Gets the currently selected language directly.
*/
Expand All @@ -151,7 +165,7 @@ export interface PiletLocaleApi {
* The translations will be exclusively used for retrieving translations for the pilet.
* @param messages The messages to use as translation basis.
*/
setTranslations(messages: LocalizationMessages): void;
setTranslations(messages: LocalizationMessages | NestedLocalizationMessages): void;
/**
* Gets the currently provided translations by the pilet.
*/
Expand Down
17 changes: 5 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2443,11 +2443,6 @@
dependencies:
"@types/node" "*"

"@types/get-value@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/get-value/-/get-value-3.0.5.tgz#4ea0e0b0a31c256636b3e7e0026c2ad38baea6f6"
integrity sha512-+o8nw0TId5cDwtdVrhlc8rvzaxbCU+JksFeu8ZunY9vUaODxngXiNceTFj2gkSwGWNRpe3PtaSWt1y0VB71PvA==

"@types/glob@*":
version "8.1.0"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc"
Expand Down Expand Up @@ -6081,6 +6076,11 @@ flat@^5.0.2:
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==

flat@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/flat/-/flat-6.0.1.tgz#09070cf918293b401577f20843edeadf4d3e8755"
integrity sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==

flexsearch@^0.6.32:
version "0.6.32"
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.6.32.tgz#1e20684d317af65baa445cdd9864a5f5b320f510"
Expand Down Expand Up @@ -6355,13 +6355,6 @@ [email protected]:
resolved "https://registry.yarnpkg.com/get-them-args/-/get-them-args-1.3.2.tgz#74a20ba8a4abece5ae199ad03f2bcc68fdfc9ba5"
integrity sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==

get-value@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8"
integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==
dependencies:
isobject "^3.0.1"

git-raw-commits@^2.0.8:
version "2.0.11"
resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723"
Expand Down

0 comments on commit 76a4d1f

Please sign in to comment.