chore: create standalone i18n utility for platform libraries#23386
chore: create standalone i18n utility for platform libraries#23386
Conversation
WalkthroughAdds a new packages/platform/libraries/i18n.ts that provides on-demand, cached remote translation loading and per-locale/per-namespace i18n instances. It normalizes locales (e.g., zh → zh-CN), validates against configured locales with fallback to "en", restricts namespaces to "common", and fetches translations from WEBAPP_URL/static/locales/{locale}/{ns}.json using fetchWithTimeout (30s in dev, 3s otherwise) with no-store. Successful loads are cached; failures return empty objects. Exports loadTranslations and getTranslation. Also adds a tsconfig path alias for @calcom/lib/server/i18n and Vite aliases mapping multiple server/i18n import paths to the new i18n.ts. Possibly related PRs
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
E2E results are ready! |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
packages/platform/libraries/i18n.ts (3)
1-1: Tighten types and await init to avoid races; return a typed TFunction.Improves safety and prevents rare timing issues.
-import { createInstance } from "i18next"; +import { createInstance, type ResourceLanguage, type TFunction, type i18n as I18nInstance } from "i18next"; -const translationCache = new Map<string, Record<string, string>>(); -const i18nInstanceCache = new Map<string, any>(); +const translationCache = new Map<string, ResourceLanguage>(); +const i18nInstanceCache = new Map<string, I18nInstance>(); -export const getTranslation = async (locale: string, ns: string) => { +export const getTranslation = async (locale: string, ns: string): Promise<TFunction> => { 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({ + await _i18n.init({ lng: locale, resources: { [locale]: { [ns]: resources, }, }, fallbackLng: "en", });Also applies to: 11-12, 80-101
24-24: Prefer configured defaultLocale over hardcoded "en".Keeps behavior aligned with repo config if default changes.
- locale = i18n.locales.includes(locale) ? locale : "en"; + locale = i18n.locales.includes(locale) ? locale : (i18n.defaultLocale || "en");
67-71: Consider caching the empty-object failure result for a short TTL.Prevents hammering the endpoint during outages. An LRU/TTL cache layer would help; optional if outages are rare.
I can wire a tiny TTL cache (e.g., Map + timestamps) if you want.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/platform/libraries/i18n.ts(1 hunks)packages/platform/libraries/tsconfig.json(1 hunks)packages/platform/libraries/vite.config.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/platform/libraries/vite.config.jspackages/platform/libraries/i18n.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/platform/libraries/i18n.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/platform/libraries/i18n.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/features/insights/components/booking/LeastCompletedBookings.tsx:31-31
Timestamp: 2025-08-21T05:55:35.161Z
Learning: Cal.com uses an automated i18n system with lingo.dev that automatically propagates new translation keys from en/common.json to all other locale files and creates PRs with proper translations. The system includes a check-missing-translations.ts script that adds English placeholders for missing keys, and a GitHub workflow that triggers lingo.dev automation to translate and create PRs.
🧬 Code graph analysis (1)
packages/platform/libraries/i18n.ts (3)
packages/config/next-i18next.config.d.ts (1)
i18n(2-5)packages/lib/constants.ts (1)
WEBAPP_URL(12-18)packages/lib/fetchWithTimeout.ts (1)
fetchWithTimeout(1-13)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Production builds / Build Web App
- GitHub Check: Production builds / Build API v2
- GitHub Check: Tests / Unit
- GitHub Check: Type check / check-types
- GitHub Check: Production builds / Build API v1
- GitHub Check: Linters / lint
| const SUPPORTED_NAMESPACES = ["common"]; | ||
|
|
There was a problem hiding this comment.
Don’t force namespace to “common”; support any ns and only default when undefined.
Restricting to ["common"] will break non-common namespaces. Let callers request any ns; default to "common" only if blank.
Apply:
-const SUPPORTED_NAMESPACES = ["common"];
+// Allow any namespace; default to "common" when not provided.
-export async function loadTranslations(_locale: string, _ns: string): Promise<Record<string, string>> {
+export async function loadTranslations(_locale: string, _ns?: string): Promise<Record<string, string>> {
let locale = _locale === "zh" ? "zh-CN" : _locale;
locale = i18n.locales.includes(locale) ? locale : "en";
- const ns = SUPPORTED_NAMESPACES.includes(_ns) ? _ns : "common";
+ const ns = _ns?.trim() || "common";Also applies to: 25-26
🤖 Prompt for AI Agents
In packages/platform/libraries/i18n.ts around lines 13-14 (and similarly lines
25-26), the code currently forces SUPPORTED_NAMESPACES = ["common"] which
prevents callers from requesting other namespaces; change the logic to allow any
namespace passed in and only substitute "common" when the incoming namespace is
undefined or empty. Update the constant/validation so it no longer hardcodes
["common"], and adjust the code paths at lines 25-26 to default to "common" only
when ns is falsy, otherwise use the provided ns as-is.
packages/platform/libraries/i18n.ts
Outdated
| translationCache.set(fallbackCacheKey, englishTranslations); | ||
| return englishTranslations; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Also cache English fallback under the original locale key to avoid repeat fetches.
Without this, every call for the failing locale re-hits the network.
const englishTranslations = await fallbackResponse.json();
translationCache.set(fallbackCacheKey, englishTranslations);
+ translationCache.set(cacheKey, englishTranslations);
return englishTranslations;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| translationCache.set(fallbackCacheKey, englishTranslations); | |
| return englishTranslations; | |
| } | |
| const englishTranslations = await fallbackResponse.json(); | |
| translationCache.set(fallbackCacheKey, englishTranslations); | |
| translationCache.set(cacheKey, englishTranslations); | |
| return englishTranslations; |
🤖 Prompt for AI Agents
In packages/platform/libraries/i18n.ts around lines 59-61, when falling back to
English you currently only cache under the fallbackCacheKey; modify the fallback
logic to also cache the English translations under the original requested locale
key so subsequent requests for that locale use the cached English copy instead
of re-fetching; specifically, after obtaining englishTranslations call
translationCache.set(locale, englishTranslations) (in addition to existing
fallbackCacheKey set) before returning.
| "experimentalDecorators": true, | ||
| "baseUrl": ".", | ||
| "paths": { | ||
| "@calcom/lib/server/i18n": ["./i18n.ts"] | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Align TS alias use; verify no relative “server/i18n” imports are relied on in TS.
TS can’t alias relative specifiers like "./server/i18n" or "../server/i18n". Vite covers those, but TS won’t. Ensure library code only uses "@calcom/lib/server/i18n" in TypeScript.
Run to find any relative imports that TS won’t resolve:
🏁 Script executed:
#!/bin/bash
rg -nP --glob '!**/dist/**' --type=ts --type=tsx -C0 \
$'from\\s+[\'"](?:\\./|\\.\\./)+server/i18n[\'"]|require\\([\'"](?:\\./|\\.\\./)+server/i18n[\'"]\\)'Length of output: 182
🏁 Script executed:
#!/bin/bash
# Search for any relative “server/i18n” imports in .ts and .tsx files (excluding dist)
rg -n -C0 --glob '!**/dist/**' -g '*.ts' -g '*.tsx' "from\s+['\"](?:\./|\.\./)+server/i18n['\"]|require\(['\"](?:\./|\.\./)+server/i18n['\"]\)"Length of output: 351
Ensure TS path alias is used for server/i18n imports
The following relative imports bypass the TypeScript path mapping ("@calcom/lib/server/i18n" → "./i18n.ts") and will fail to resolve in IDEs and during compilation. Please update them to use the alias:
- packages/lib/buildCalEventFromBooking.ts:6
- import { getTranslation } from "./server/i18n";
- import { getTranslation } from "@calcom/lib/server/i18n";
- packages/lib/tests/buildCalEventFromBooking.test.ts:7
- import { getTranslation } from "../server/i18n";
- import { getTranslation } from "@calcom/lib/server/i18n";
This change aligns your imports with tsconfig.json and avoids relying on Vite-only alias resolution.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "experimentalDecorators": true, | |
| "baseUrl": ".", | |
| "paths": { | |
| "@calcom/lib/server/i18n": ["./i18n.ts"] | |
| } | |
| ++ b/packages/lib/buildCalEventFromBooking.ts | |
| @@ (around line 6) | |
| import { getTranslation } from "@calcom/lib/server/i18n"; |
| "experimentalDecorators": true, | |
| "baseUrl": ".", | |
| "paths": { | |
| "@calcom/lib/server/i18n": ["./i18n.ts"] | |
| } | |
| ++ b/packages/lib/__tests__/buildCalEventFromBooking.test.ts | |
| @@ (around line 7) | |
| import { getTranslation } from "@calcom/lib/server/i18n"; |
🤖 Prompt for AI Agents
In packages/platform/libraries/tsconfig.json around lines 11–15, the TS path
alias "@calcom/lib/server/i18n" => "./i18n.ts" is declared but two files are
still using relative imports; update imports in
packages/lib/buildCalEventFromBooking.ts (line ~6) and
packages/lib/__tests__/buildCalEventFromBooking.test.ts (line ~7) to import from
"@calcom/lib/server/i18n" instead of the relative path so the TypeScript path
mapping is used consistently (also run a quick build/IDE reload to verify
resolution).
| "@calcom/lib/server/i18n": path.resolve(__dirname, "./i18n.ts"), | ||
| "./server/i18n": path.resolve(__dirname, "./i18n.ts"), | ||
| "../server/i18n": path.resolve(__dirname, "./i18n.ts"), |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Aliases look good; confirm code paths no longer depend on relative TS resolution.
Vite will resolve the three aliases at build-time. Ensure TypeScript imports stick to "@calcom/lib/server/i18n" (tsconfig can’t alias the relative ones).
List all i18n import specifiers to confirm:
🏁 Script executed:
#!/bin/bash
rg -nP -C0 $'from\\s+[\'"](@calcom/lib/server/i18n|(?:\\./|\\.\\./)+server/i18n)[\'"]|require\\([\'"](@calcom/lib/server/i18n|(?:\\./|\\.\\./)+server/i18n)[\'"]\\)'Length of output: 10175
Action Required: Replace Remaining Relative i18n Imports
The ripgrep output shows two files still importing ./server/i18n via relative paths, which will not be covered by your Vite aliases and aren’t supported by TypeScript’s path mapping. Please update both to use the alias "@calcom/lib/server/i18n":
• packages/lib/buildCalEventFromBooking.ts (line 6)
- import { getTranslation } from "./server/i18n";
+ import { getTranslation } from "@calcom/lib/server/i18n";• packages/lib/tests/buildCalEventFromBooking.test.ts (line 7)
- import { getTranslation } from "../server/i18n";
+ import { getTranslation } from "@calcom/lib/server/i18n";Once these are updated, all imports will consistently use the aliased path and avoid build- or runtime-resolution issues.
🤖 Prompt for AI Agents
In packages/lib/buildCalEventFromBooking.ts (line 6) and
packages/lib/__tests__/buildCalEventFromBooking.test.ts (line 7), two imports
still reference the relative path "./server/i18n" which bypasses the Vite alias
and TypeScript path mapping; change those import statements to use the alias
"@calcom/lib/server/i18n" instead so both runtime and TS resolve consistently
(update any import declarations or require calls to the aliased path).
What does this PR do?
This pull request introduces a new, self-contained i18n.ts utility within the @calcom/platform library package. This new module is designed to handle translation loading independently by fetching locale files exclusively over HTTP.
To make this work seamlessly, this PR also:
Why are these changes needed?
Previously, library packages that needed to handle translations were tightly coupled to the Next.js application's file structure and its i18n implementation. This created problems when building these packages with Vite for use in different environments, as the local file paths (e.g., require("../../../apps/web/...")) would break.
This new approach creates a decoupled i18n solution that:
Mandatory Tasks (DO NOT REMOVE)