import { createInstance } from "i18next"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { fetchWithTimeout } from "../fetchWithTimeout"; import logger from "../logger"; /* eslint-disable @typescript-eslint/no-var-requires */ const { i18n } = require("@calcom/config/next-i18next.config"); const log = logger.getSubLogger({ prefix: ["[i18n]"] }); // Import only English translations directly to avoid HTTP requests // Other languages will be loaded dynamically to minimize bundle size const englishTranslations: Record< string, string > = require("../../../apps/web/public/static/locales/en/common.json"); const translationCache = new Map>(); const i18nInstanceCache = new Map(); const SUPPORTED_NAMESPACES = ["common"]; /** * Loads translations for a specific locale and namespace with optimized caching * English translations are bundled as englishTranslations for reliability, * other languages use dynamic imports with HTTP fallback to minimize bundle size * @param {string} _locale - The locale code (e.g., 'en', 'fr', 'zh') * @param {string} ns - The namespace for the translations * @returns {Promise>} Translations object or fallback translations on failure */ export async function loadTranslations(_locale: string, _ns: string) { let locale = _locale === "zh" ? "zh-CN" : _locale; locale = i18n.locales.includes(locale) ? locale : "en"; const ns = SUPPORTED_NAMESPACES.includes(_ns) ? _ns : "common"; const cacheKey = `${locale}-${ns}`; if (translationCache.has(cacheKey)) { return translationCache.get(cacheKey); } if (locale === "en") { translationCache.set(cacheKey, englishTranslations); return englishTranslations; } try { const { default: localeTranslations } = await import( `../../../apps/web/public/static/locales/${locale}/${ns}.json` ); translationCache.set(cacheKey, localeTranslations); return localeTranslations; } catch (dynamicImportErr) { log.warn(`Dynamic import failed for locale ${locale}:`, dynamicImportErr); // Try HTTP fallback as second option try { const response = await fetchWithTimeout( `${WEBAPP_URL}/static/locales/${locale}/${ns}.json`, { cache: "no-store", }, 3000 ); if (response.ok) { const httpTranslations = await response.json(); translationCache.set(cacheKey, httpTranslations); return httpTranslations; } } catch (httpErr) { log.error(`HTTP fallback also failed for locale ${locale}:`, httpErr); } log.info(`Falling back to English for locale: ${locale}`); return englishTranslations; } } /** * 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} 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); };