Created
August 25, 2023 14:43
-
-
Save buren/29fccf9c55e2b9fa748439df7fc9bcc2 to your computer and use it in GitHub Desktop.
Super minimalistic I18n lib in TypeScript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"title": "Example title", | |
"about": { | |
"title": "Example about title" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const flattenObject = (obj: Record<string, any>, parentKey?: string) => { | |
let result: Record<string, any> = {}; | |
Object.keys(obj).forEach((key) => { | |
const value = obj[key]; | |
const _key = parentKey ? parentKey + "." + key : key; | |
if (typeof value === "object") { | |
result = { ...result, ...flattenObject(value, _key) }; | |
} else { | |
result[_key] = value; | |
} | |
}); | |
return result; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ObjectLeaves } from "@/types"; | |
import { flattenObject } from "./flatten-object"; | |
import en from "./en.json"; | |
export enum Locale { | |
en = "en", | |
} | |
type TranslationsData = typeof en; | |
export type I18nKey = ObjectLeaves<TranslationsData>; | |
type I18nLocaleData = Record<I18nKey, string>; | |
type I18nData = { [key in Locale]: I18nLocaleData }; | |
const i18nData: I18nData = { | |
[Locale.en]: flattenObject(en) as I18nLocaleData, | |
}; | |
type InterpolationValue = string | number; | |
type Interpolation = Record<string, InterpolationValue>; | |
type I18nOptions = { | |
locale?: Locale; | |
fallback?: string; | |
}; | |
const interpolate = ( | |
translation: string, | |
interpolation: Interpolation = {} | |
): string => | |
Object.keys(interpolation).reduce( | |
(acc, key) => | |
acc.replaceAll(`{${key}}`, interpolation[key].toString()), | |
translation | |
); | |
export const currentLocale = () => Locale.en; | |
export const currentLocaleWithTerritory = () => `${Locale.en}_US`; | |
export function i18n( | |
i18nKey: I18nKey, | |
interpolation: Interpolation = {}, | |
{ | |
fallback, | |
locale = currentLocale(), | |
}: I18nOptions = { locale: currentLocale() }) { | |
const translation = i18nData[locale][i18nKey] || fallback; | |
if (!translation) { | |
throw new Error(`Can't find translation for i18nKey: ${i18nKey}`); | |
} | |
return interpolate(translation, interpolation); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The `Join` type concatenates two types K and P with a dot (.) separator. | |
* However, if the second type is an empty string, it avoids adding the dot. | |
* This type is used for joining nested object keys into a single string. | |
* It works only if both K and P are either strings or numbers. | |
* | |
* Examples: | |
* Join<'a', 'b'> -> 'a.b' | |
* Join<'a', ''> -> 'a' | |
*/ | |
type Join<K, P> = K extends string | number | |
? P extends string | number | |
? `${K}${"" extends P ? "" : "."}${P}` | |
: never | |
: never; | |
/** | |
* The `Prev` type is a tuple where each index contains its value. | |
* It can be used to decrement a number type. It is a mechanism to implement | |
* recursion depth control. The ellipsis with 0[] at the end allows it to | |
* accept numbers greater than 20, but it won't have exact mappings for them. | |
*/ | |
type Prev = [ | |
never, | |
0, | |
1, | |
2, | |
3, | |
4, | |
5, | |
6, | |
7, | |
8, | |
9, | |
10, | |
11, | |
12, | |
13, | |
14, | |
15, | |
16, | |
17, | |
18, | |
19, | |
20, | |
...0[], | |
]; | |
/** | |
* The `ObjectLeaves` type recursively retrieves the paths of all the leaves | |
* (terminal nodes) of a nested object as strings. | |
* The recursion depth is controlled by the D parameter (default is 10). | |
* | |
* - If D is never, it returns never. | |
* - If T is an object, it recursively processes the properties of the object, | |
* joining the keys with dots using the `Join` type. | |
* - If T is not an object, it returns an empty string. | |
* | |
* Examples: | |
* ObjectLeaves<{a: {b: number}}> -> 'a.b' | |
* ObjectLeaves<{a: number, b: {c: string}}> -> 'a' | 'b.c' | |
*/ | |
export type ObjectLeaves<T, D extends number = 10> = [D] extends [never] | |
? never | |
: T extends object | |
? { [K in keyof T]-?: Join<K, ObjectLeaves<T[K], Prev[D]>> }[keyof T] | |
: ""; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { ReactElement, ReactNode } from "react"; | |
import { I18nKey, i18n } from "./i18n"; | |
interface TransProps { | |
i18nKey: I18nKey; | |
text?: string; | |
[x: string]: ReactNode; | |
} | |
const Trans = ({ i18nKey, text, ...interpolationData }: TransProps) => { | |
const i18nText = text ? text : i18n(i18nKey); | |
const interpolations = Object.entries(interpolationData || {}) as [string, ReactNode][]; | |
// RegExp for matching "{...}" in the text | |
const interpolationPattern = /(\{.*?\})/g; | |
// RegExp for removing the "{" and "}" characters | |
const bracesPattern = /[{}]/g; | |
return i18nText.split(interpolationPattern).map((part, index) => { | |
const key = part.replace(bracesPattern, ""); | |
const node = interpolations.find(([iKey]) => iKey === key)?.[1]; | |
return node | |
? React.cloneElement(node as ReactElement<any>, { key: index }) | |
: part; | |
}); | |
}; | |
export { Trans }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment