Skip to content

Comments

chore: create standalone i18n utility for platform libraries#23386

Merged
supalarry merged 7 commits intomainfrom
custom-i18n-platform-libs
Aug 27, 2025
Merged

chore: create standalone i18n utility for platform libraries#23386
supalarry merged 7 commits intomainfrom
custom-i18n-platform-libs

Conversation

@ThyMinimalDev
Copy link
Contributor

@ThyMinimalDev ThyMinimalDev commented Aug 27, 2025

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:

Adds a Vite alias to intercept any imports for @calcom/lib/server/i18n within this package and redirect them to the new local i18n.ts file.

Adds a corresponding paths alias to the package's tsconfig.json to ensure TypeScript and IDEs can correctly resolve the module for type-checking and development.

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:

Enables Portability: Allows library code to be used in any environment (Next.js, Vite, Node.js scripts) because it has no dependency on the web app's file system.

Simplifies Builds: Resolves module resolution errors in Vite by providing a clear, overridable path for i18n logic.

Improves Modularity: Encapsulates translation-fetching logic within the package that needs it, adhering to better monorepo principles.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

@ThyMinimalDev ThyMinimalDev requested a review from a team August 27, 2025 11:44
@ThyMinimalDev ThyMinimalDev requested a review from a team as a code owner August 27, 2025 11:44
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Walkthrough

Adds 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 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.

📥 Commits

Reviewing files that changed from the base of the PR and between af4e9c2 and 44e185d.

📒 Files selected for processing (1)
  • packages/platform/libraries/i18n.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/platform/libraries/i18n.ts
⏰ 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 Atoms
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Type check / check-types
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Tests / Unit
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch custom-i18n-platform-libs

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@keithwillcode keithwillcode added core area: core, team members only foundation platform Anything related to our platform plan labels Aug 27, 2025
@vercel
Copy link

vercel bot commented Aug 27, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 27, 2025 1:20pm
cal-eu Ignored Ignored Aug 27, 2025 1:20pm

@dosubot dosubot bot added the i18n area: i18n, translations label Aug 27, 2025
@github-actions github-actions bot marked this pull request as draft August 27, 2025 11:51
@ThyMinimalDev ThyMinimalDev requested a review from volnei August 27, 2025 11:52
@ThyMinimalDev ThyMinimalDev marked this pull request as ready for review August 27, 2025 11:52
@ThyMinimalDev ThyMinimalDev changed the title chore: create i18n for platform libraries chore: create standalone i18n utility for platform libraries Aug 27, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Aug 27, 2025

E2E results are ready!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 2d9162a and af4e9c2.

📒 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.js
  • packages/platform/libraries/i18n.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field 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

Comment on lines +13 to +14
const SUPPORTED_NAMESPACES = ["common"];

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines 59 to 61
translationCache.set(fallbackCacheKey, englishTranslations);
return englishTranslations;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +11 to +15
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@calcom/lib/server/i18n": ["./i18n.ts"]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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.

Suggested change
"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";
Suggested change
"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).

Comment on lines +190 to +192
"@calcom/lib/server/i18n": path.resolve(__dirname, "./i18n.ts"),
"./server/i18n": path.resolve(__dirname, "./i18n.ts"),
"../server/i18n": path.resolve(__dirname, "./i18n.ts"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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).

@supalarry supalarry merged commit f18ebc4 into main Aug 27, 2025
41 checks passed
@supalarry supalarry deleted the custom-i18n-platform-libs branch August 27, 2025 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only foundation i18n area: i18n, translations platform Anything related to our platform plan ready-for-e2e

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants