import type { i18n as I18nInstance } from 'i18next' import type { Locale } from '.' import type { NamespaceCamelCase, NamespaceKebabCase } from './i18next-config' import { match } from '@formatjs/intl-localematcher' import { camelCase, kebabCase } from 'es-toolkit/compat' import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' import Negotiator from 'negotiator' import { cookies, headers } from 'next/headers' import { initReactI18next } from 'react-i18next/initReactI18next' import serverOnlyContext from '@/utils/server-only-context' import { i18n } from '.' const [getLocaleCache, setLocaleCache] = serverOnlyContext(null) const [getI18nInstance, setI18nInstance] = serverOnlyContext(null) const getOrCreateI18next = async (lng: Locale) => { let instance = getI18nInstance() if (instance) return instance instance = createInstance() await instance .use(initReactI18next) .use(resourcesToBackend((language: Locale, namespace: NamespaceCamelCase | NamespaceKebabCase) => { const fileNamespace = kebabCase(namespace) as NamespaceKebabCase return import(`../i18n/${language}/${fileNamespace}.json`) })) .init({ lng, fallbackLng: 'en-US', keySeparator: false, }) setI18nInstance(instance) return instance } export async function getTranslation(lng: Locale, ns: NamespaceKebabCase) { const camelNs = camelCase(ns) as NamespaceCamelCase const i18nextInstance = await getOrCreateI18next(lng) if (!i18nextInstance.hasLoadedNamespace(camelNs)) await i18nextInstance.loadNamespaces(camelNs) return { t: i18nextInstance.getFixedT(lng, camelNs), i18n: i18nextInstance, } } export const getLocaleOnServer = async (): Promise => { const cached = getLocaleCache() if (cached) return cached const locales: string[] = i18n.locales let languages: string[] | undefined // get locale from cookie const localeCookie = (await cookies()).get('locale') languages = localeCookie?.value ? [localeCookie.value] : [] if (!languages.length) { // Negotiator expects plain object so we need to transform headers const negotiatorHeaders: Record = {}; (await headers()).forEach((value, key) => (negotiatorHeaders[key] = value)) // Use negotiator and intl-localematcher to get best locale languages = new Negotiator({ headers: negotiatorHeaders }).languages() } // Validate languages if (!Array.isArray(languages) || languages.length === 0 || !languages.every(lang => typeof lang === 'string' && /^[\w-]+$/.test(lang))) languages = [i18n.defaultLocale] // match locale const matchedLocale = match(languages, locales, i18n.defaultLocale) as Locale setLocaleCache(matchedLocale) return matchedLocale }