localization
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "i18n",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createContext, useMemo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { I18nContextValue, InterpolationParams, TranslationMap } from './types';
|
||||
import { interpolate, resolveKey } from './resolve';
|
||||
|
||||
export const I18nContext = createContext<I18nContextValue>({
|
||||
t: (key: string) => key,
|
||||
locale: 'en',
|
||||
});
|
||||
|
||||
type I18nProviderProps = {
|
||||
translations: TranslationMap;
|
||||
locale: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const I18nProvider = ({ translations, locale, children }: I18nProviderProps) => {
|
||||
const value = useMemo<I18nContextValue>(() => {
|
||||
const t = (key: string, params?: InterpolationParams) =>
|
||||
interpolate(resolveKey(translations, locale, key), params);
|
||||
return { t, locale };
|
||||
}, [translations, locale]);
|
||||
|
||||
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export { I18nProvider, I18nContext } from './I18nProvider';
|
||||
export { useTranslation } from './useTranslation';
|
||||
export { resolveKey, interpolate } from './resolve';
|
||||
export type { TranslationMap, InterpolationParams, I18nContextValue, TranslationKeys } from './types';
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { InterpolationParams, TranslationMap } from './types';
|
||||
|
||||
export const resolveKey = (translations: TranslationMap, locale: string, key: string): string => {
|
||||
const value = walkPath(translations[locale], key) ?? walkPath(translations['en'], key);
|
||||
return typeof value === 'string' ? value : key;
|
||||
};
|
||||
|
||||
const walkPath = (obj: unknown, path: string): unknown => {
|
||||
let current = obj;
|
||||
for (const segment of path.split('.')) {
|
||||
if (current == null || typeof current !== 'object') return undefined;
|
||||
current = (current as Record<string, unknown>)[segment];
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
export const interpolate = (template: string, params?: InterpolationParams): string => {
|
||||
if (!params) return template;
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (_, key: string) => String(params[key] ?? `{{${key}}}`));
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
export type TranslationMap = Record<string, Record<string, unknown>>;
|
||||
|
||||
export type InterpolationParams = Record<string, string | number>;
|
||||
|
||||
export type I18nContextValue = {
|
||||
t: (key: string, params?: InterpolationParams) => string;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export type TranslationKeys<T, Prefix extends string = ''> = T extends string
|
||||
? Prefix
|
||||
: T extends Record<string, unknown>
|
||||
? {
|
||||
[K in keyof T & string]: TranslationKeys<T[K], Prefix extends '' ? K : `${Prefix}.${K}`>;
|
||||
}[keyof T & string]
|
||||
: never;
|
||||
@@ -0,0 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { I18nContext } from './I18nProvider';
|
||||
|
||||
export const useTranslation = () => useContext(I18nContext);
|
||||
Reference in New Issue
Block a user