From 65a89d44ba14859a7f96c4b29b9bc075ff6369e0 Mon Sep 17 00:00:00 2001 From: Fabien Drault Date: Tue, 6 Aug 2024 10:56:57 +0200 Subject: [PATCH] docs(i18n): Add 2 examples of i18n usage with i18n-typegen --- README.md | 34 ++--------- docs/custom.md | 155 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/i18n-js.md | 96 ++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 30 deletions(-) create mode 100644 docs/custom.md create mode 100644 docs/i18n-js.md diff --git a/README.md b/README.md index 5b016a6..c5558e4 100644 --- a/README.md +++ b/README.md @@ -113,38 +113,12 @@ translate("goodbye"); // OK - Styling without trouble your translations on React and React-Native [styled-tagged-text](https://github.com/BeTomorrow/styled-tagged-text) - Works great with [i18n-js](https://github.com/fnando/i18n-js) -### Example of my implementation over i18n-js +### Example of implementation -```typescript -import { I18n } from "i18n-js"; -import { TranslationFunction } from "translations"; -// Import generated type from translations.d.ts - -type MyCustomI18n = Omit & { - t: TranslationFunction; - - /** - * Same as `t` without any type checking. - * Should be used only when the translation key cannot be statically inferred. - */ - unsafeTranslate: (key: string, interpolations?: Record) => string; -}; +See `docs` for complete usage of type generation with some i18n implementations -class MyInternationalization extends I18n { - unsafeTranslate(key: string, interpolations?: Record) { - return this.t(key, interpolations); - } -} - -export const i18n = new MyInternationalization( - { - fr, - en, - }, - { locale: getUserLanguage() } -) as MyCustomI18n; -// ^ Apply my custom type to enjoy static translations and interpolations check ! -``` +- i18n-js [docs/i18n-js.md](docs/i18n-js.md) +- custom implemetation [docs/custom.md](docs/custom.md) ## Contribution diff --git a/docs/custom.md b/docs/custom.md new file mode 100644 index 0000000..b6374be --- /dev/null +++ b/docs/custom.md @@ -0,0 +1,155 @@ +# Custom i18n implementation + +## Translations + +`en.json`: + +```json +{ + "animals": { + "one": "{{count}} animal", + "other": "{{count}} animals", + "zero": "No animal" + }, + "lorem": "Culpa et aliquip proident adipisicing in." +} +``` + +## Usage + +```ts +t("animals", { count: 2 }); // typesafe +t("lorem"); +``` + +# Custom implementation + +Since i18n-typegen is a type generator only, you have to provide your own i18n solution. + +Here is a very simple example of i18n implementation that take big advantages from typing: + +```ts +import type { TranslationFunction } from "translations"; + +const logger = LoggerManager.getInstance("[🌐 I18n]"); + +let currentLangData = require("translations/en.json"); + +export const setLanguage = (data: any) => { + currentLangData = data; +}; + +const pluralizer = (count: number) => { + switch (count) { + case 0: + return ["zero", "one", "other"]; + case 1: + return ["one", "other"]; + default: + return ["other"]; + } +}; + +const findPartsForData = ( + data: any, + parts: string[], + interpolations?: { [key: string]: string | number } | null +) => { + for (let index = 0; index < parts.length; ++index) { + const part = parts[index]; + if (data[part] === undefined) { + return undefined; + } + data = data[part]; + } + const count = interpolations?.count; + if (typeof count === "number") { + const keys = pluralizer(count); + while (keys.length) { + const key = keys.shift() as string; + if (data[key] !== undefined) { + data = data[key]; + break; + } + } + } + if (typeof data !== "string") { + if (typeof data === "number") { + return String(data); + } + return undefined; + } + return data; +}; + +const translate: TranslationFunction = ( + path: string, + interpolations?: { [key: string]: string | number } | null +) => { + if (path === undefined || path === null) { + console.error(new Error("[🌐 I18n] Invalid translation key.").message); + return ""; + } + const parts = path.split("."); + let translation = findPartsForData(currentLangData, parts, interpolations); + if (translation === undefined) { + logger.error(`[🌐 I18n] Can't find translation for "${path}"`); + return path; + } + + if (interpolations) { + for (const interpolation in interpolations) { + translation = translation.replace( + `{{${interpolation}}}`, + String(interpolations[interpolation]) + ); + } + } + return translation; +}; + +const unsafeTranslate = translate as ( + key: string, + interpolations?: { [key: string]: string | number } +) => string; + +export { translate as t, unsafeTranslate }; +``` + +- `t` enforce type safety +- `unsafeTranslate` can be used for dynamic keys, when type safety can be a burden + +# Configuration + +```json +{ + "input": { + "format": "nested", + "path": "./i18n/fr.json" + }, + "output": { + "path": "./i18n/translations.d.ts" + }, + "rules": [ + { + "//": "Add pluralization placeholders", + "condition": { "keyEndsWith": ["zero", "one", "other"] }, + "transformer": { + "addPlaceholder": { "name": "count", "type": ["number"] }, + "removeLastPart": true + } + }, + { + "//": "Add interpolation values for matched placeholders", + "condition": { "placeholderPattern": { "prefix": "{{", "suffix": "}}" } }, + "transformer": { + "addMatchedPlaceholder": { "type": ["string", "number"] } + } + } + ], + "extra": { + "prettierIgnore": true, + "eslintDisablePrettier": false + } +} +``` diff --git a/docs/i18n-js.md b/docs/i18n-js.md new file mode 100644 index 0000000..46c80b2 --- /dev/null +++ b/docs/i18n-js.md @@ -0,0 +1,96 @@ +### Example of my implementation over i18n-js + +Package: [https://www.npmjs.com/package/i18n-js](https://www.npmjs.com/package/i18n-js) + +```json +{ + "animals": { + "one": "{{count}} animal", + "other": "{{count}} animals", + "zero": "No animal" + }, + "lorem": "Culpa et aliquip proident adipisicing in." +} +``` + +## Usage + +```ts +i18n.t("animals", { count: 2 }); // count is mandatory +i18n.t("lorem"); +``` + +### Example of my implementation over i18n-js + +```typescript +import { I18n } from "i18n-js"; +import { TranslationFunction } from "translations"; +// Import generated type from translations.d.ts + +type MyCustomI18n = Omit & { + t: TranslationFunction; + + /** + * Same as `t` without any type checking. + * Should be used only when the translation key cannot be statically inferred. + */ + unsafeTranslate: (key: string, interpolations?: Record) => string; +}; + +class MyInternationalization extends I18n { + unsafeTranslate(key: string, interpolations?: Record) { + return this.t(key, interpolations); + } +} + +export const i18n = new MyInternationalization( + { + fr, + en, + }, + { locale: getUserLanguage() } // <- load the user language by default +) as MyCustomI18n; +// ^ Apply my custom type to enjoy static translations and interpolations check ! +``` + +### Default configuration for i18n-js + +```json +{ + "input": { + "format": "nested", + "path": "./translations/en.json" + }, + "output": { + "path": "./translations/translations.d.ts" + }, + "rules": [ + { + "//": "Add pluralization placeholders", + "condition": { "keyEndsWith": ["zero", "one", "other"] }, + "transformer": { + "addPlaceholder": { "name": "count", "type": ["number", "string"] }, + "removeLastPart": true + } + }, + { + "//": "Add interpolation values for matched placeholders", + "condition": { "placeholderPattern": { "prefix": "{{", "suffix": "}}" } }, + "transformer": { + "addMatchedPlaceholder": { "type": ["string", "number"] } + } + }, + { + "//": "Add interpolation values for matched placeholders using percent", + "condition": { "placeholderPattern": { "prefix": "%{", "suffix": "}" } }, + "transformer": { + "addMatchedPlaceholder": { "type": ["string", "number"] } + } + } + ], + "extra": { + "prettierIgnore": true, + "eslintDisablePrettier": false + } +} +```