This commit is contained in:
giancarlo
2024-03-24 02:23:22 +08:00
parent 648d77b430
commit bce3479368
589 changed files with 37067 additions and 9596 deletions

View File

@@ -0,0 +1,48 @@
{
"name": "@kit/i18n",
"private": true,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
"./server": "./src/i18n.server.ts",
"./client": "./src/i18n.client.ts",
"./cookie": "./src/get-language-cookie.ts",
"./provider": "./src/I18nProvider.tsx"
},
"dependencies": {
"@kit/shared": "^0.1.0"
},
"devDependencies": {
"@kit/prettier-config": "0.1.0",
"@kit/eslint-config": "0.2.0",
"@kit/tailwind-config": "0.1.0",
"@kit/tsconfig": "0.1.0"
},
"peerDependencies": {
"i18next": "^23.10.1",
"react-i18next": "^14.1.0",
"i18next-browser-languagedetector": "7.2.0",
"i18next-resources-to-backend": "^1.2.0",
"next": "^14.1.4"
},
"eslintConfig": {
"root": true,
"extends": [
"@kit/eslint-config/base",
"@kit/eslint-config/react"
]
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
}
}

View File

@@ -0,0 +1,42 @@
'use client';
import type { i18n } from 'i18next';
let client: i18n;
type Resolver = (
lang: string,
namespace: string,
) => Promise<Record<string, string>>;
export function I18nProvider({
lang,
children,
resolver,
}: React.PropsWithChildren<{
lang: string;
resolver: Resolver;
}>) {
if (!client) {
throw withI18nClient(lang, resolver);
}
return children;
}
async function withI18nClient(lang: string, resolver: Resolver) {
if (typeof window !== 'undefined') {
client = await loadClientI18n(lang, resolver);
} else {
const { initializeServerI18n } = await import('./i18n.server');
client = await initializeServerI18n(lang, resolver);
}
}
async function loadClientI18n(lang: string | undefined, resolver: Resolver) {
// TODO: pull cookie client-side
const { initializeI18nClient } = await import('./i18n.client');
return initializeI18nClient(lang, resolver);
}

View File

@@ -0,0 +1,9 @@
import { cookies } from 'next/headers';
import { I18N_COOKIE_NAME } from './i18n.settings';
function getLanguageCookie() {
return cookies().get(I18N_COOKIE_NAME)?.value;
}
export default getLanguageCookie;

View File

@@ -0,0 +1,54 @@
import i18next, { i18n } from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import resourcesToBackend from 'i18next-resources-to-backend';
import { initReactI18next } from 'react-i18next';
import { I18N_COOKIE_NAME, getI18nSettings } from './i18n.settings';
let promise: Promise<i18n>;
export function initializeI18nClient(
lng: string | undefined,
i18nResolver: (lang: string, namespace: string) => Promise<object>,
): Promise<i18n> {
const settings = getI18nSettings(lng);
if (promise !== undefined) {
return promise;
}
promise = new Promise<i18n>((resolve, reject) => {
void i18next
.use(initReactI18next)
.use(
resourcesToBackend(async (language, namespace, callback) => {
const data = await i18nResolver(language, namespace);
return callback(null, data);
}),
)
.use(LanguageDetector)
.init(
{
...settings,
detection: {
order: ['htmlTag', 'cookie', 'navigator'],
caches: ['cookie'],
lookupCookie: I18N_COOKIE_NAME,
},
interpolation: {
escapeValue: false,
},
},
(err) => {
if (err) {
return reject(err);
}
resolve(i18next);
},
);
});
return promise;
}

View File

@@ -0,0 +1,35 @@
import { createInstance } from 'i18next';
import resourcesToBackend from 'i18next-resources-to-backend';
import { initReactI18next } from 'react-i18next/initReactI18next';
import { getI18nSettings } from './i18n.settings';
export async function initializeServerI18n(
lang: string | undefined,
i18nResolver: (language: string, namespace: string) => Promise<object>,
) {
const i18nInstance = createInstance();
const settings = getI18nSettings(lang);
await i18nInstance
.use(initReactI18next)
.use(
resourcesToBackend(async (language, namespace, callback) => {
try {
const data = await i18nResolver(language, namespace);
return callback(null, data);
} catch (error) {
console.log(
`Error loading i18n file: locales/${language}/${namespace}.json`,
error,
);
return {};
}
}),
)
.init(settings);
return i18nInstance;
}

View File

@@ -0,0 +1,45 @@
import { InitOptions } from 'i18next';
const fallbackLng = 'en';
const languages: string[] = [fallbackLng];
export const I18N_COOKIE_NAME = 'lang';
/**
* The default array of Internationalization (i18n) namespaces.
* These namespaces are commonly used in the application for translation purposes.
*
* Add your own namespaces here
**/
export const defaultI18nNamespaces = [
'common',
'auth',
'organization',
'profile',
'subscription',
'onboarding',
];
export function getI18nSettings(
language: string | undefined,
ns: string | string[] = defaultI18nNamespaces,
): InitOptions {
let lng = language ?? fallbackLng;
if (!languages.includes(lng)) {
console.warn(
`Language "${lng}" is not supported. Falling back to "${fallbackLng}"`,
);
lng = fallbackLng;
}
return {
supportedLngs: languages,
fallbackLng,
lng,
fallbackNS: defaultI18nNamespaces,
defaultNS: defaultI18nNamespaces,
ns,
};
}

View File

@@ -0,0 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}