From f18ebc4d67de7a209386a9766825149ab02a3c09 Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:55:57 +0300 Subject: [PATCH] chore: create standalone i18n utility for platform libraries (#23386) * chore: create i18n for platform libraries * fixup! chore: create i18n for platform libraries * refactor: use previous version of i18n * Revert "refactor: use previous version of i18n" This reverts commit 0181ae1018d99ea6d5455d6dde5f3727e70422e1. * refactor: use previous version of i18n --------- Co-authored-by: supalarry --- packages/platform/libraries/i18n.ts | 83 ++++++++++++++++++++++ packages/platform/libraries/tsconfig.json | 6 +- packages/platform/libraries/vite.config.js | 3 + 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 packages/platform/libraries/i18n.ts diff --git a/packages/platform/libraries/i18n.ts b/packages/platform/libraries/i18n.ts new file mode 100644 index 0000000000..1f993ecd8b --- /dev/null +++ b/packages/platform/libraries/i18n.ts @@ -0,0 +1,83 @@ +import { createInstance } from "i18next"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { fetchWithTimeout } from "@calcom/lib/fetchWithTimeout"; +import logger from "@calcom/lib/logger"; + +/* eslint-disable @typescript-eslint/no-var-requires */ +const { i18n } = require("@calcom/config/next-i18next.config"); + +const translationCache = new Map>(); +const i18nInstanceCache = new Map(); +const SUPPORTED_NAMESPACES = ["common"]; + +/** + * 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>} 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); + } + + const url = `${WEBAPP_URL}/static/locales/${locale}/${ns}.json`; + try { + const response = await fetchWithTimeout( + url, + { + cache: "no-store", + }, + process.env.NODE_ENV === "development" ? 30000 : 3000 + ); + + if (!response.ok) { + logger.error(`Failed to fetch translations: ${response.status}`); + return {}; + } + + const translations = await response.json(); + translationCache.set(cacheKey, translations); + return translations; + } catch (err) { + console.error("loadTranslations Error:", err); + + return {}; + } +} + +/** + * 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); +}; diff --git a/packages/platform/libraries/tsconfig.json b/packages/platform/libraries/tsconfig.json index 4bedb18d6d..0f046d85bc 100644 --- a/packages/platform/libraries/tsconfig.json +++ b/packages/platform/libraries/tsconfig.json @@ -8,7 +8,11 @@ "types": ["jest"], "outDir": "./dist", "emitDecoratorMetadata": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "@calcom/lib/server/i18n": ["./i18n.ts"] + } }, "include": [".", "../../types/*.d.ts"], "exclude": [ diff --git a/packages/platform/libraries/vite.config.js b/packages/platform/libraries/vite.config.js index 494cb703ea..528efd1efa 100644 --- a/packages/platform/libraries/vite.config.js +++ b/packages/platform/libraries/vite.config.js @@ -187,6 +187,9 @@ export default defineConfig({ plugins: [react(), dts()], resolve: { alias: { + "@calcom/lib/server/i18n": path.resolve(__dirname, "./i18n.ts"), + "./server/i18n": path.resolve(__dirname, "./i18n.ts"), + "../server/i18n": path.resolve(__dirname, "./i18n.ts"), "@": path.resolve(__dirname, "./src"), "@calcom/lib": path.resolve(__dirname, "../../lib"), "@calcom/trpc": resolve("../../trpc"),