Files
calendar/packages/lib/server/i18n.ts
T
Benny JooandGitHub b17b5a169f perf: make useLocale hook to use server-fetched i18n if in App router context (#20597)
* write I18nProvider

* rename

* update useLocale

* fix

* use a singleton

* safer

* better

* fix e2e test 1

* fix e2e test 2

* fix e2e test 3

* fix e2e test 4

* fix

* refactor

* address comments

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix flaky test

* add more caching

* revert changes to e2e tests

* add jsdoc

* memoize

* update window object with new translations in /settings/my-account/general

* remove dead code

* only use locale and ns as deps

* fix
2025-04-11 14:28:26 -07:00

101 lines
3.2 KiB
TypeScript

import { createInstance } from "i18next";
import { WEBAPP_URL } from "@calcom/lib/constants";
const translationCache = new Map<string, Record<string, string>>();
const i18nInstanceCache = new Map<string, any>();
/**
* Loads English fallback translations for when requested locale translations fail
* Implements caching to avoid redundant network requests
* @returns {Promise<Record<string, string>>} English translations object or empty object on failure
*/
async function loadFallbackTranslations() {
const cacheKey = "en-common";
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey);
}
try {
const res = await fetch(`${WEBAPP_URL}/static/locales/en/common.json`, {
cache: process.env.NODE_ENV === "production" ? "force-cache" : "no-store",
});
if (!res.ok) {
throw new Error(`Failed to fetch fallback translations: ${res.status}`);
}
const translations = await res.json();
translationCache.set(cacheKey, translations);
return translations;
} catch (error) {
console.error("Could not fetch fallback translations:", error);
return {};
}
}
/**
* Loads translations for a specific locale and namespace with optimized caching
* @param {string} _locale - The locale code (e.g., 'en', 'fr', 'zh')
* @param {string} ns - The namespace for the translations
* @returns {Promise<Record<string, string>>} Translations object or fallback translations on failure
*/
export async function loadTranslations(_locale: string, ns: string) {
const locale = _locale === "zh" ? "zh-CN" : _locale;
const cacheKey = `${locale}-${ns}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey);
}
try {
const url = `${WEBAPP_URL}/static/locales/${locale}/${ns}.json`;
const response = await fetch(url, {
cache: process.env.NODE_ENV === "production" ? "force-cache" : "no-store",
});
if (!response.ok) {
throw new Error(`Failed to fetch translations: ${response.status}`);
}
const translations = await response.json();
translationCache.set(cacheKey, translations);
return translations;
} catch (error) {
console.warn(`Failed to load translations for ${locale}/${ns}, falling back to English:`, error);
const fallbackTranslations = await loadFallbackTranslations();
return fallbackTranslations;
}
}
/**
* Creates or retrieves a cached i18next translation function for the specified locale and namespace
* @param {string} locale - The locale code (e.g., 'en', 'fr')
* @param {string} ns - The namespace for the translations
* @returns {Promise<Function>} A translation function bound to the specified locale and namespace
*/
export const getTranslation = async (locale: string, ns: string) => {
const cacheKey = `${locale}-${ns}`;
if (i18nInstanceCache.has(cacheKey)) {
return i18nInstanceCache.get(cacheKey).getFixedT(locale, ns);
}
const resources = await loadTranslations(locale, ns);
const _i18n = createInstance();
_i18n.init({
lng: locale,
resources: {
[locale]: {
[ns]: resources,
},
},
fallbackLng: "en",
});
// Cache the i18n instance
i18nInstanceCache.set(cacheKey, _i18n);
return _i18n.getFixedT(locale, ns);
};