From d19b7f84851fd5d348daf2c8ad7ece7b2f12b28e Mon Sep 17 00:00:00 2001
From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com>
Date: Fri, 19 Jun 2026 17:46:17 +0530
Subject: [PATCH] Enable getting started translations (#21842)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
The Getting Started pages on the docs site (docs.twenty.com) were only
ever available in English, never translated into the other supported
languages.
**Root cause:** The Getting Started section (added in #19728) was never
added to the Crowdin source config (`crowdin-docs.yml`), so its `.mdx`
files were never uploaded for translation. Only `user-guide`,
`developers`, and `twenty-ui` were configured.
This also surfaced a related bug: because the pages had no translations,
the navigation generator fell back to the English page path for every
language, duplicating paths like `getting-started/introduction` across
all 14 language navs. Mintlify treats duplicate cross-language paths as
undefined behavior, which broke the language switcher (it always
redirected to `/getting-started/introduction`).
## Changes
- `.github/crowdin-docs.yml` — add `getting-started/**/*.mdx` as a
translation source so the pages get sent to Crowdin.
- `packages/twenty-docs/scripts/fix-translated-links.sh` — add
`getting-started` link-rewriting rules to match the other sections.
- `packages/twenty-docs/scripts/generate-docs-json.ts` — only include a
page in a non-default language when its translated file exists; drop
empty groups/tabs (removes the duplicate cross-language paths that broke
the switcher).
- `packages/twenty-docs/docs.json` — regenerated.
---------
Co-authored-by: Cursor
---
.github/crowdin-docs.yml | 4 +
packages/twenty-docs/docs.json | 423 +-----------------
.../scripts/fix-translated-links.sh | 13 +-
.../twenty-docs/scripts/generate-docs-json.ts | 51 ++-
4 files changed, 47 insertions(+), 444 deletions(-)
diff --git a/.github/crowdin-docs.yml b/.github/crowdin-docs.yml
index df1570f88a..6ce8b3203b 100644
--- a/.github/crowdin-docs.yml
+++ b/.github/crowdin-docs.yml
@@ -9,6 +9,10 @@
"base_path": ".."
files: [
+ {
+ "source": "packages/twenty-docs/getting-started/**/*.mdx",
+ "translation": "packages/twenty-docs/l/%two_letters_code%/getting-started/**/%original_file_name%",
+ },
{
#
# MDX documentation files - user-guide
diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json
index e512d59155..22f79f2849 100644
--- a/packages/twenty-docs/docs.json
+++ b/packages/twenty-docs/docs.json
@@ -468,32 +468,6 @@
{
"language": "fr",
"tabs": [
- {
- "tab": "Prise en main",
- "groups": [
- {
- "group": "Bienvenue",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Concepts clés",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Guide de l'utilisateur",
"groups": [
@@ -901,32 +875,6 @@
{
"language": "ar",
"tabs": [
- {
- "tab": "البدء",
- "groups": [
- {
- "group": "مرحبًا",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "المفاهيم الأساسية",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "دليل المستخدم",
"groups": [
@@ -1334,32 +1282,6 @@
{
"language": "cs",
"tabs": [
- {
- "tab": "Začínáme",
- "groups": [
- {
- "group": "Vítejte",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Základní pojmy",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Uživatelská příručka",
"groups": [
@@ -1767,32 +1689,6 @@
{
"language": "de",
"tabs": [
- {
- "tab": "Erste Schritte",
- "groups": [
- {
- "group": "Willkommen",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Kernkonzepte",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Benutzerhandbuch",
"groups": [
@@ -2200,32 +2096,6 @@
{
"language": "es",
"tabs": [
- {
- "tab": "Primeros pasos",
- "groups": [
- {
- "group": "Bienvenido",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Conceptos clave",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Guía de usuario",
"groups": [
@@ -2633,32 +2503,6 @@
{
"language": "it",
"tabs": [
- {
- "tab": "Per iniziare",
- "groups": [
- {
- "group": "Benvenuto",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Concetti chiave",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Guida utente",
"groups": [
@@ -3066,32 +2910,6 @@
{
"language": "ja",
"tabs": [
- {
- "tab": "Getting Started",
- "groups": [
- {
- "group": "Welcome",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Core Concepts",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "ユーザーガイド",
"groups": [
@@ -3215,8 +3033,7 @@
"l/ja/user-guide/workflows/how-tos/crm-automations/formula-fields",
"l/ja/user-guide/workflows/how-tos/crm-automations/display-related-record-data",
"l/ja/user-guide/workflows/how-tos/crm-automations/closed-won-automations",
- "l/ja/user-guide/workflows/how-tos/crm-automations/detect-stale-opportunities",
- "user-guide/workflows/how-tos/crm-automations/auto-reply-to-inbound-emails"
+ "l/ja/user-guide/workflows/how-tos/crm-automations/detect-stale-opportunities"
]
},
{
@@ -3272,11 +3089,9 @@
"group": "Layout",
"icon": "table-columns",
"pages": [
- "user-guide/layout/overview",
{
"group": "Reference",
"pages": [
- "user-guide/layout/capabilities/navigation",
{
"group": "Views",
"pages": [
@@ -3287,8 +3102,7 @@
"l/ja/user-guide/views-pipelines/capabilities/fields-and-columns",
"l/ja/user-guide/views-pipelines/capabilities/view-settings"
]
- },
- "user-guide/layout/capabilities/record-pages"
+ }
]
},
{
@@ -3402,79 +3216,6 @@
"l/ja/developers/introduction"
]
},
- {
- "group": "Apps",
- "pages": [
- {
- "group": "Getting Started",
- "pages": [
- "developers/extend/apps/getting-started/quick-start",
- "developers/extend/apps/getting-started/concepts",
- "developers/extend/apps/getting-started/project-structure",
- "developers/extend/apps/getting-started/local-server",
- "developers/extend/apps/getting-started/scaffolding",
- "developers/extend/apps/getting-started/troubleshooting"
- ]
- },
- {
- "group": "Config",
- "pages": [
- "developers/extend/apps/config/overview",
- "developers/extend/apps/config/application",
- "developers/extend/apps/config/roles",
- "developers/extend/apps/config/install-hooks",
- "developers/extend/apps/config/public-assets"
- ]
- },
- {
- "group": "Data",
- "pages": [
- "developers/extend/apps/data/overview",
- "developers/extend/apps/data/objects",
- "developers/extend/apps/data/extending-objects",
- "developers/extend/apps/data/relations"
- ]
- },
- {
- "group": "Logic",
- "pages": [
- "developers/extend/apps/logic/overview",
- "developers/extend/apps/logic/logic-functions",
- "developers/extend/apps/logic/skills-and-agents",
- "developers/extend/apps/logic/connections"
- ]
- },
- {
- "group": "Layout",
- "pages": [
- "developers/extend/apps/layout/overview",
- "developers/extend/apps/layout/views",
- "developers/extend/apps/layout/navigation-menu-items",
- "developers/extend/apps/layout/page-layouts",
- "developers/extend/apps/layout/front-components",
- "developers/extend/apps/layout/command-menu-items"
- ]
- },
- {
- "group": "Operations",
- "pages": [
- "developers/extend/apps/operations/overview",
- "developers/extend/apps/operations/cli",
- "developers/extend/apps/operations/sync-and-recovery",
- "developers/extend/apps/operations/testing",
- "developers/extend/apps/operations/publishing"
- ]
- }
- ]
- },
- {
- "group": "API",
- "pages": [
- "developers/extend/api",
- "developers/extend/webhooks",
- "developers/extend/oauth"
- ]
- },
{
"group": "セルフホスト",
"pages": [
@@ -3487,9 +3228,7 @@
{
"group": "貢献",
"pages": [
- "l/ja/developers/contribute/capabilities/local-setup",
- "developers/contribute/commands",
- "developers/contribute/style-guide"
+ "l/ja/developers/contribute/capabilities/local-setup"
]
}
]
@@ -3499,32 +3238,6 @@
{
"language": "ko",
"tabs": [
- {
- "tab": "시작하기",
- "groups": [
- {
- "group": "환영합니다",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "핵심 개념",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "사용자 안내서",
"groups": [
@@ -3932,32 +3645,6 @@
{
"language": "pt",
"tabs": [
- {
- "tab": "Primeiros passos",
- "groups": [
- {
- "group": "Boas-vindas",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Conceitos essenciais",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "User Guide",
"groups": [
@@ -4365,32 +4052,6 @@
{
"language": "ro",
"tabs": [
- {
- "tab": "Începeți",
- "groups": [
- {
- "group": "Bun venit",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Concepte cheie",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "User Guide",
"groups": [
@@ -4798,32 +4459,6 @@
{
"language": "ru",
"tabs": [
- {
- "tab": "Начало работы",
- "groups": [
- {
- "group": "Добро пожаловать",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Основные понятия",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Руководство пользователя",
"groups": [
@@ -5231,32 +4866,6 @@
{
"language": "tr",
"tabs": [
- {
- "tab": "Başlarken",
- "groups": [
- {
- "group": "Hoş geldiniz",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "Temel Kavramlar",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "Kullanıcı Rehberi",
"groups": [
@@ -5664,32 +5273,6 @@
{
"language": "zh",
"tabs": [
- {
- "tab": "开始使用",
- "groups": [
- {
- "group": "欢迎",
- "pages": [
- "getting-started/introduction",
- "getting-started/key-features",
- "getting-started/quickstart"
- ]
- },
- {
- "group": "核心概念",
- "pages": [
- "getting-started/core-concepts/data-model",
- "getting-started/core-concepts/layout",
- "getting-started/core-concepts/workflows",
- "getting-started/core-concepts/calendar-and-email",
- "getting-started/core-concepts/ai",
- "getting-started/core-concepts/apps",
- "getting-started/core-concepts/dashboards",
- "getting-started/core-concepts/glossary"
- ]
- }
- ]
- },
{
"tab": "用户指南",
"groups": [
diff --git a/packages/twenty-docs/scripts/fix-translated-links.sh b/packages/twenty-docs/scripts/fix-translated-links.sh
index 8b0cda33d8..87bd2927a1 100755
--- a/packages/twenty-docs/scripts/fix-translated-links.sh
+++ b/packages/twenty-docs/scripts/fix-translated-links.sh
@@ -26,6 +26,8 @@ for lang_dir in "$DOCS_DIR"/*/ ; do
echo "📝 Processing $lang_code documentation..."
+ find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
+ "s|href=\"/getting-started/|href=\"/l/$lang_code/getting-started/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|href=\"/user-guide/|href=\"/l/$lang_code/user-guide/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
@@ -33,6 +35,8 @@ for lang_dir in "$DOCS_DIR"/*/ ; do
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|href=\"/twenty-ui/|href=\"/l/$lang_code/twenty-ui/|g" {} \;
+ find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
+ "s|](/getting-started/|](/l/$lang_code/getting-started/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|](/user-guide/|](/l/$lang_code/user-guide/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
@@ -40,6 +44,8 @@ for lang_dir in "$DOCS_DIR"/*/ ; do
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|](/twenty-ui/|](/l/$lang_code/twenty-ui/|g" {} \;
+ find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
+ "s|https://docs\.twenty\.com/getting-started/|https://docs.twenty.com/l/$lang_code/getting-started/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|https://docs\.twenty\.com/user-guide/|https://docs.twenty.com/l/$lang_code/user-guide/|g" {} \;
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
@@ -47,13 +53,6 @@ for lang_dir in "$DOCS_DIR"/*/ ; do
find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
"s|https://docs\.twenty\.com/twenty-ui/|https://docs.twenty.com/l/$lang_code/twenty-ui/|g" {} \;
- find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
- "s|https://docs\.twenty\.com/user-guide/|https://docs.twenty.com/$lang_code/user-guide/|g" {} \;
- find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
- "s|https://docs\.twenty\.com/developers/|https://docs.twenty.com/$lang_code/developers/|g" {} \;
- find "$lang_dir" -name "*.mdx" -type f -exec sed -i.bak \
- "s|https://docs\.twenty\.com/twenty-ui/|https://docs.twenty.com/$lang_code/twenty-ui/|g" {} \;
-
find "$lang_dir" -name "*.bak" -type f -delete
echo "✅ $lang_code documentation links fixed"
diff --git a/packages/twenty-docs/scripts/generate-docs-json.ts b/packages/twenty-docs/scripts/generate-docs-json.ts
index 60be8acda2..a239538cbc 100644
--- a/packages/twenty-docs/scripts/generate-docs-json.ts
+++ b/packages/twenty-docs/scripts/generate-docs-json.ts
@@ -113,37 +113,54 @@ const buildLanguageEntry = (language: string): GeneratedLanguage => {
return {
language,
- tabs: baseStructure.tabs.map((tab) => ({
- tab: translationMaps.tabLabels.get(tab.key) ?? tab.label,
- groups: tab.groups.map((group) =>
- buildGroup(group, translationMaps, language),
- ),
- })),
+ tabs: baseStructure.tabs
+ .map((tab) => ({
+ tab: translationMaps.tabLabels.get(tab.key) ?? tab.label,
+ groups: tab.groups
+ .map((group) => buildGroup(group, translationMaps, language))
+ .filter((group): group is GeneratedGroup => group !== null),
+ }))
+ .filter((tab) => tab.groups.length > 0),
};
};
+// Mintlify requires each page path to appear in only one language's navigation.
+// Duplicating a path across languages breaks the language switcher (it can no
+// longer resolve the equivalent page and falls back to the first page). So a
+// page is only included in a non-default language when its translation exists,
+// and empty groups/tabs are dropped entirely.
const buildGroup = (
group: BaseGroup,
translations: TranslationMaps,
language: string,
-): GeneratedGroup => ({
- group: translations.groupLabels.get(group.key) ?? group.label,
- ...(group.icon ? { icon: group.icon } : {}),
- pages: group.pages.map((page) =>
- typeof page === 'string'
- ? formatPageSlug(page, language)
- : buildGroup(page, translations, language),
- ),
-});
+): GeneratedGroup | null => {
+ const pages = group.pages
+ .map((page) =>
+ typeof page === 'string'
+ ? formatPageSlug(page, language)
+ : buildGroup(page, translations, language),
+ )
+ .filter((page): page is string | GeneratedGroup => page !== null);
-const formatPageSlug = (slug: string, language: string): string => {
+ if (pages.length === 0) {
+ return null;
+ }
+
+ return {
+ group: translations.groupLabels.get(group.key) ?? group.label,
+ ...(group.icon ? { icon: group.icon } : {}),
+ pages,
+ };
+};
+
+const formatPageSlug = (slug: string, language: string): string | null => {
if (language === DEFAULT_LANGUAGE) {
return slug;
}
const localizedPagePath = path.join(localesRoot, language, `${slug}.mdx`);
- return fs.existsSync(localizedPagePath) ? `l/${language}/${slug}` : slug;
+ return fs.existsSync(localizedPagePath) ? `l/${language}/${slug}` : null;
};
const hasLocaleContent = (language: string): boolean => {