fix: refactor i18n loadTranslations and set timeout to 3s (#22633)

* refactor

* use abort controller

* address comment

* fix tests

* fix time

* fix tests
This commit is contained in:
Benny Joo
2025-08-01 22:30:28 +09:00
committed by GitHub
parent aa618dbaa2
commit 6ff20b98f4
9 changed files with 96 additions and 53 deletions
@@ -26,7 +26,12 @@ vi.mock("@calcom/emails/email-manager", () => {
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: (key: string) => key,
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
@@ -25,6 +25,17 @@ vi.mock("@calcom/prisma", () => {
vi.mock("stripe");
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
vi.mock("@calcom/lib/constants", async () => {
const actual = (await vi.importActual("@calcom/lib/constants")) as typeof import("@calcom/lib/constants");
return {
@@ -43,6 +43,17 @@ vi.mock("@calcom/lib/domainManager/organization", () => ({
createDomain: vi.fn(),
}));
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
const mockOrganizationOnboarding = {
name: "Test Org",
slug: "test-org",
@@ -17,6 +17,16 @@ vi.mock("@calcom/features/ee/workflows/lib/reminders/providers/emailProvider", (
sendOrScheduleWorkflowEmails: vi.fn(),
}));
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
describe("reminderScheduler", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -1,5 +1,5 @@
import { createInstance } from "i18next";
import { expect, test, describe } from "vitest";
import { vi, expect, test, describe } from "vitest";
import { getTranslation } from "@calcom/lib/server/i18n";
import { TimeFormat } from "@calcom/lib/timeFormat";
@@ -10,6 +10,17 @@ import { getTemplateBodyForAction } from "../actionHelperFunctions";
import compareReminderBodyToTemplate from "../compareReminderBodyToTemplate";
import plainTextReminderTemplates from "../reminders/templates/plainTextTemplates";
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
const translation = async () => {
const _i18n = createInstance();
await _i18n.init({
+13
View File
@@ -0,0 +1,13 @@
export async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs: number) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
return response;
} finally {
clearTimeout(timeout);
}
}
+23 -47
View File
@@ -1,39 +1,14 @@
import { createInstance } from "i18next";
import { i18n } from "@calcom/config/next-i18next.config";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { fetchWithTimeout } from "../fetchWithTimeout";
import logger from "../logger";
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 {};
}
}
const SUPPORTED_NAMESPACES = ["common"];
/**
* Loads translations for a specific locale and namespace with optimized caching
@@ -41,32 +16,33 @@ async function loadFallbackTranslations() {
* @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;
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);
}
try {
const url = `${WEBAPP_URL}/static/locales/${locale}/${ns}.json`;
const response = await fetch(url, {
const url = `${WEBAPP_URL}/static/locales/${locale}/${ns}.json`;
const response = await fetchWithTimeout(
url,
{
cache: process.env.NODE_ENV === "production" ? "force-cache" : "no-store",
});
},
3000
);
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;
if (!response.ok) {
logger.error(`Failed to fetch translations: ${response.status}`);
return {};
}
const translations = await response.json();
translationCache.set(cacheKey, translations);
return translations;
}
/**
+5 -2
View File
@@ -9,8 +9,11 @@ import { UserRepository } from "./user";
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: (key: string) => {
return () => key;
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});
@@ -11,8 +11,11 @@ import { UserCreationService } from "./userCreationService";
vi.mock("@calcom/lib/server/i18n", () => {
return {
getTranslation: (key: string) => {
return () => key;
getTranslation: async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
},
};
});