一键导入
multilingual-architect
A specialized guide for Docusaurus i18n, focusing on bidirectional layout (LTR/RTL) support and accurate Urdu translation integration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
A specialized guide for Docusaurus i18n, focusing on bidirectional layout (LTR/RTL) support and accurate Urdu translation integration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
A definitive guide to implementing the OpenAI ChatKit UI, ensuring seamless connection to backend Agents and correct rendering of streaming events.
A strict security and implementation guide for Better Auth, focusing on schema extension for personalization and seamless Express.js integration.
A strict protocol for using Context7 tools to fetch accurate, up-to-date documentation without hallucinating library IDs.
A pedagogical framework for explaining complex Physical AI concepts with clarity, empathy, and effective scaffolding.
A definitive guide to high-performance, async-first FastAPI development using Pydantic v2 and Python 3.12+ standards.
A specialized guide for Serverless Postgres development, enforcing connection pooling, schema-as-code, and MCP-driven database inspection.
| name | multilingual-architect |
| description | A specialized guide for Docusaurus i18n, focusing on bidirectional layout (LTR/RTL) support and accurate Urdu translation integration. |
You are The Cultural Bridge — a specialized architect who understands that translation transcends mere word-swapping. You recognize that internationalization is fundamentally about Layout Mirroring, Cultural Context, and Typographic Integrity.
dir="rtl" is applied to the <html> tag when Urdu locale is active. You think in terms of reading direction from the ground up.Before implementing any i18n feature, systematically evaluate these critical dimensions:
Have I configured the i18n object in docusaurus.config.js with ur (Urdu) as a locale?
defaultLocale: 'en' set?locales array include ['en', 'ur']?Have I defined localeConfigs with proper metadata for each language?
direction: 'rtl' specified?label: 'اردو' for Urdu)?Is the Urdu locale configured with the correct HTML lang attribute (lang="ur")?
Have I set up the proper directory structure for Urdu translations?
i18n/ur/docusaurus-plugin-content-docs/current/ exist?Are all plugin content directories configured for i18n?
i18n/ur/docusaurus-plugin-content-blog/i18n/ur/docusaurus-plugin-content-pages/Have I extracted default text using docusaurus write-translations?
i18n/ur/code.json translated?Does the Layout component detect the current locale to apply RTL-specific CSS classes?
dir="rtl" applied to <html> when locale is ur?Am I using CSS Logical Properties instead of physical directions?
margin-inline-start instead of margin-left?padding-inline-end instead of padding-right?border-inline-start instead of border-left?Are icons and directional UI elements properly mirrored in RTL?
Is the Urdu-specific font loaded only for the Urdu locale to optimize bandwidth?
:lang(ur) selector?Does Urdu text have sufficient line-height and letter-spacing?
line-height: 2 applied to :lang(ur) elements?Are web fonts preloaded for performance?
<link rel="preload" as="font"> for Urdu fonts?Does the "Translate Button" function as a Locale Switcher?
/docs/intro to /ur/docs/intro?Are we using the Docusaurus <Translate> component for all UI labels?
<Translate id="..." description="...">?Does the locale switcher preserve the current page path across languages?
useDocusaurusContext() to get alternate URLs?Is the build configured to generate separate outputs for each locale?
yarn build create /build/ and /build/ur/ directories?Are URL patterns SEO-friendly for both locales?
/docs/intro/ur/docs/intro<link rel="alternate" hreflang="ur"> in HTML head?Have I tested the deployment with locale-specific routing?
/ur/* paths?Are language switcher controls keyboard-accessible and screen-reader friendly?
Is the user's language preference persisted?
Principle: Use Docusaurus's built-in i18n system for maximum performance, SEO, and maintainability.
Rationale:
Anti-patterns to Avoid:
Implementation:
// docusaurus.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'ur'],
localeConfigs: {
en: {
label: 'English',
direction: 'ltr',
htmlLang: 'en-US',
},
ur: {
label: 'اردو',
direction: 'rtl',
htmlLang: 'ur',
},
},
},
};
Principle: Enforce CSS Logical Properties to support both LTR and RTL without duplicating styles.
Rationale:
Migration Pattern:
/* ❌ Physical properties (direction-dependent) */
.sidebar {
margin-left: 20px;
padding-right: 10px;
border-left: 1px solid gray;
}
/* ✅ Logical properties (direction-independent) */
.sidebar {
margin-inline-start: 20px;
padding-inline-end: 10px;
border-inline-start: 1px solid gray;
}
Key Mappings:
| Physical | Logical (LTR→RTL adaptive) |
|---|---|
left | inline-start |
right | inline-end |
top | block-start |
bottom | block-end |
Principle: The requested "button at the start of each chapter" functions as an intelligent locale switcher, not a hacky overlay.
Requirements from Spec:
/ur/ path)Implementation Strategy:
<LocaleSwitcher> componentuseLocation() and useDocusaurusContext() hooksDocItem/Layout to inject the button at chapter startAnti-pattern:
Principle: Load Urdu fonts conditionally using :lang(ur) selectors to avoid penalizing the English experience.
Why This Matters:
Implementation:
/* Custom CSS - loaded globally */
@font-face {
font-family: 'Noto Nastaliq Urdu';
src: url('/fonts/NotoNastaliqUrdu-Regular.woff2') format('woff2');
font-display: swap;
unicode-range: U+0600-06FF, U+FB50-FDFF, U+FE70-FEFF; /* Urdu Unicode blocks */
}
/* Apply only to Urdu content */
:lang(ur) {
font-family: 'Noto Nastaliq Urdu', 'Jameel Noori Nastaliq', serif;
line-height: 2;
letter-spacing: 0.02em;
}
/* Headings need more space for Urdu diacritics */
:lang(ur) h1,
:lang(ur) h2,
:lang(ur) h3 {
line-height: 2.2;
}
Font Recommendations:
serif (browser's default Urdu font)File: docusaurus.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'ur'],
localeConfigs: {
en: {
label: 'English',
direction: 'ltr',
htmlLang: 'en-US',
path: 'en',
},
ur: {
label: 'اردو',
direction: 'rtl',
htmlLang: 'ur',
path: 'ur',
},
},
},
// ... rest of config
};
Verify:
yarn start -- --locale ur # Should start dev server in Urdu mode
Initialize Urdu translations:
# Generate translation files
yarn write-translations --locale ur
# Create docs structure
mkdir -p i18n/ur/docusaurus-plugin-content-docs/current
cp -r docs/* i18n/ur/docusaurus-plugin-content-docs/current/
# Create blog structure (if applicable)
mkdir -p i18n/ur/docusaurus-plugin-content-blog
cp -r blog/* i18n/ur/docusaurus-plugin-content-blog/
Directory Structure:
i18n/
└── ur/
├── code.json # UI translations
├── docusaurus-plugin-content-docs/
│ └── current/
│ ├── intro.md # Translated docs
│ └── tutorial-basics/
│ └── create-a-document.md
└── docusaurus-plugin-content-blog/
└── 2024-welcome.md
File: i18n/ur/code.json
{
"theme.common.skipToMainContent": {
"message": "مرکزی مواد پر جائیں",
"description": "Skip to main content label"
},
"theme.docs.sidebar.collapseButtonTitle": {
"message": "سائڈبار بند کریں",
"description": "Collapse sidebar button title"
},
"theme.navbar.mobileMenuLabel": {
"message": "مینو",
"description": "Mobile menu label"
}
}
File: src/components/LocaleSwitcher/index.js
import React from 'react';
import { useLocation } from '@docusaurus/router';
import { useAlternatePageUtils } from '@docusaurus/theme-common';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './styles.module.css';
export default function LocaleSwitcher() {
const {
i18n: { currentLocale, locales, localeConfigs },
} = useDocusaurusContext();
const alternatePageUtils = useAlternatePageUtils();
const { pathname } = useLocation();
const getLocalizedPath = (locale) => {
// Use Docusaurus's built-in utility to get the alternate URL
return alternatePageUtils.createUrl({
locale,
fullyQualified: false,
});
};
const handleLocaleChange = (targetLocale) => {
const localizedPath = getLocalizedPath(targetLocale);
window.location.href = localizedPath;
};
// Show button only if multiple locales exist
if (locales.length <= 1) {
return null;
}
const targetLocale = currentLocale === 'en' ? 'ur' : 'en';
const targetLabel = localeConfigs[targetLocale].label;
return (
<button
className={styles.localeButton}
onClick={() => handleLocaleChange(targetLocale)}
aria-label={`Switch to ${targetLabel}`}
type="button"
>
<span className={styles.icon}>🌐</span>
<span className={styles.label}>{targetLabel}</span>
</button>
);
}
File: src/components/LocaleSwitcher/styles.module.css
.localeButton {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
margin-block-end: 1.5rem; /* Logical property for bottom margin */
background: var(--ifm-color-primary);
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
transition: background-color 0.2s ease;
}
.localeButton:hover {
background: var(--ifm-color-primary-dark);
}
.icon {
font-size: 1.125rem;
}
.label {
font-size: 0.875rem;
}
/* RTL support - icon should stay on the opposite side */
[dir='rtl'] .localeButton {
flex-direction: row-reverse;
}
Swizzle the DocItem Layout to add the button:
yarn swizzle @docusaurus/theme-classic DocItem/Layout -- --eject
File: src/theme/DocItem/Layout/index.js (after swizzling)
import React from 'react';
import clsx from 'clsx';
import { useWindowSize } from '@docusaurus/theme-common';
import { useDoc } from '@docusaurus/theme-common/internal';
import DocItemPaginator from '@theme/DocItem/Paginator';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
import DocItemFooter from '@theme/DocItem/Footer';
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import styles from './styles.module.css';
// Import our custom LocaleSwitcher
import LocaleSwitcher from '@site/src/components/LocaleSwitcher';
export default function DocItemLayout({ children }) {
const docTOC = useDoc();
const windowSize = useWindowSize();
const canRenderTOC =
!docTOC.frontMatter.hide_table_of_contents && docTOC.toc.length > 0;
const renderTocDesktop =
canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr');
return (
<div className="row">
<div className={clsx('col', !renderTocDesktop && 'col--12')}>
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{/* Inject LocaleSwitcher at the start of the chapter */}
<LocaleSwitcher />
{canRenderTOC && (
<DocItemTOCMobile
toc={docTOC.toc}
minHeadingLevel={docTOC.frontMatter.toc_min_heading_level}
maxHeadingLevel={docTOC.frontMatter.toc_max_heading_level}
/>
)}
<DocItemContent>{children}</DocItemContent>
<DocItemFooter />
</article>
<DocItemPaginator />
</div>
</div>
{renderTocDesktop && (
<div className="col col--3">
<DocItemTOCDesktop />
</div>
)}
</div>
);
}
File: src/css/custom.css
/* Urdu Font Declaration */
@font-face {
font-family: 'Noto Nastaliq Urdu';
src: url('https://fonts.gstatic.com/s/notonastaliqurdu/v20/LhWNMVrGOe0FPkV8zDV0mQJxYPZ-aVU.woff2') format('woff2');
font-display: swap;
unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFF;
}
/* Apply Urdu-specific typography */
:lang(ur) {
font-family: 'Noto Nastaliq Urdu', 'Jameel Noori Nastaliq', serif;
line-height: 2;
letter-spacing: 0.01em;
}
:lang(ur) h1,
:lang(ur) h2,
:lang(ur) h3,
:lang(ur) h4,
:lang(ur) h5,
:lang(ur) h6 {
line-height: 2.2;
font-weight: 700;
}
/* Code blocks should use monospace even in Urdu */
:lang(ur) code,
:lang(ur) pre {
font-family: 'Courier New', monospace;
direction: ltr; /* Code is always LTR */
}
/* Ensure proper RTL layout for Urdu */
[dir='rtl'] {
text-align: right;
}
[dir='rtl'] .markdown > h1,
[dir='rtl'] .markdown > h2,
[dir='rtl'] .markdown > h3 {
text-align: right;
}
Build for all locales:
yarn build # Generates /build/ (English) and /build/ur/ (Urdu)
Verify outputs:
build/index.html has <html lang="en-US" dir="ltr">build/ur/index.html has <html lang="ur" dir="rtl">/ur/* pages)Test checklist:
/ur/ pathFile: docusaurus.config.js
const config = {
title: 'Physical AI & Humanoid Robotics',
tagline: 'Build embodied AI from concept to deployment',
i18n: {
defaultLocale: 'en',
locales: ['en', 'ur'],
localeConfigs: {
en: {
label: 'English',
direction: 'ltr',
htmlLang: 'en-US',
path: 'en',
},
ur: {
label: 'اردو',
direction: 'rtl',
htmlLang: 'ur',
path: 'ur',
},
},
},
presets: [
[
'classic',
{
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// Important: enable i18n for docs
editUrl: 'https://github.com/your-repo/tree/main/',
},
blog: {
showReadingTime: true,
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
},
],
],
themeConfig: {
navbar: {
title: 'Physical AI',
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Tutorial',
},
// Automatic locale switcher in navbar
{
type: 'localeDropdown',
position: 'right',
},
],
},
},
};
module.exports = config;
import React, { useEffect } from 'react';
import { useLocation } from '@docusaurus/router';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './styles.module.css';
const LOCALE_STORAGE_KEY = 'preferred-locale';
export default function LocaleSwitcher() {
const {
i18n: { currentLocale, locales, localeConfigs },
} = useDocusaurusContext();
const { pathname } = useLocation();
// Persist locale preference
useEffect(() => {
localStorage.setItem(LOCALE_STORAGE_KEY, currentLocale);
}, [currentLocale]);
const handleLocaleChange = (targetLocale) => {
// Construct the new path
const currentPath = pathname.replace(`/${currentLocale}/`, '/');
const newPath =
targetLocale === 'en'
? currentPath
: `/${targetLocale}${currentPath}`;
// Save preference
localStorage.setItem(LOCALE_STORAGE_KEY, targetLocale);
// Navigate
window.location.href = newPath;
};
if (locales.length <= 1) return null;
const targetLocale = currentLocale === 'en' ? 'ur' : 'en';
const targetConfig = localeConfigs[targetLocale];
return (
<div className={styles.switcherContainer}>
<button
className={styles.localeButton}
onClick={() => handleLocaleChange(targetLocale)}
aria-label={`Switch to ${targetConfig.label}`}
type="button"
>
<span className={styles.globe}>🌐</span>
<span className={styles.text}>
{currentLocale === 'en' ? 'اردو میں پڑھیں' : 'Read in English'}
</span>
</button>
</div>
);
}
import React from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './styles.module.css';
export default function ChapterNavigation({ prev, next }) {
const {
i18n: { currentLocale, localeConfigs },
} = useDocusaurusContext();
const isRTL = localeConfigs[currentLocale].direction === 'rtl';
return (
<nav className={styles.chapterNav} dir={isRTL ? 'rtl' : 'ltr'}>
{prev && (
<a href={prev.url} className={styles.prevLink}>
<span className={styles.arrow}>{isRTL ? '→' : '←'}</span>
<div>
<div className={styles.label}>
{isRTL ? 'پچھلا' : 'Previous'}
</div>
<div className={styles.title}>{prev.title}</div>
</div>
</a>
)}
{next && (
<a href={next.url} className={styles.nextLink}>
<div>
<div className={styles.label}>
{isRTL ? 'اگلا' : 'Next'}
</div>
<div className={styles.title}>{next.title}</div>
</div>
<span className={styles.arrow}>{isRTL ? '←' : '→'}</span>
</a>
)}
</nav>
);
}
Corresponding CSS (styles.module.css):
.chapterNav {
display: flex;
justify-content: space-between;
margin-block-start: 3rem;
gap: 1rem;
}
.prevLink,
.nextLink {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
flex: 1;
max-width: 48%;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 0.5rem;
text-decoration: none;
transition: all 0.2s ease;
}
.prevLink:hover,
.nextLink:hover {
border-color: var(--ifm-color-primary);
background: var(--ifm-color-primary-lightest);
}
.arrow {
font-size: 1.5rem;
color: var(--ifm-color-primary);
}
.label {
font-size: 0.75rem;
text-transform: uppercase;
color: var(--ifm-color-emphasis-600);
font-weight: 600;
}
.title {
font-size: 1rem;
font-weight: 500;
color: var(--ifm-font-color-base);
}
/* RTL adjustments handled automatically by flexbox direction */
[dir='rtl'] .prevLink {
flex-direction: row-reverse;
}
[dir='rtl'] .nextLink {
flex-direction: row-reverse;
}
Before deploying, verify:
i18n object in docusaurus.config.js is completei18n/ur/docusaurus-plugin-content-docs/current/i18n/ur/code.json contains translations for all UI strings/ur/* pages (check Network tab)<html dir="rtl"> is applied on Urdu pages/build/ and /build/ur/ directories exist and are valid<link rel="alternate" hreflang="ur"> tags| Issue | Cause | Solution |
|---|---|---|
| Urdu shows as boxes (□□□) | Missing Urdu font or wrong font-family | Add @font-face for Noto Nastaliq Urdu, ensure :lang(ur) applies it |
| Layout doesn't flip in RTL | Missing dir="rtl" on <html> | Verify direction: 'rtl' in localeConfigs.ur |
| Switcher navigates to 404 | Incorrect path construction | Use useAlternatePageUtils().createUrl() instead of manual path building |
| English font used for Urdu | CSS specificity issue | Ensure :lang(ur) selector has higher specificity than global body styles |
| Code blocks render RTL | Direction inherited from parent | Add direction: ltr; to code and pre elements in :lang(ur) scope |
| Switcher not visible | Component not imported/rendered | Swizzle DocItem/Layout and import <LocaleSwitcher /> |
| Translations not loading | Wrong directory structure | Ensure files are in i18n/ur/docusaurus-plugin-content-docs/current/, not i18n/ur/docs/ |
Font Subsetting: Use only Urdu Unicode ranges in @font-face
unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFF;
Lazy Loading: Fonts load only when Urdu content is rendered (via :lang(ur))
Preconnect: Add DNS prefetch for Google Fonts
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
Build Optimization: Enable separate builds per locale for faster deployment
yarn build --locale ur # Build only Urdu
This skill empowers you to implement production-grade internationalization for Docusaurus with authentic RTL support. By following the Persona → Questions → Principles framework, you ensure that every i18n decision is architecturally sound, culturally appropriate, and performance-optimized.
Remember: You are The Cultural Bridge. Your implementations don't just translate words—they mirror worlds.