Originally published on LinkedIn, 14 May 2025. Republished here with light editing.
Project Overview#
This document details the process of implementing Chinese/English language switching for a Docusaurus-based website showcasing FemTech companies. The primary goal was to enable user interface elements on the /showcase page to be properly translated between English and Chinese.
Table of Contents#
- Initial Requirements
- Testing Environment
- Attempted Approaches
- Successful Solution
- File Structure Overview
- Implementation Details
- Challenges Encountered
- Lessons Learned
- Best Practices and Recommendations
Initial Requirements#
- Enable language switching between English (default) and Chinese (zh-Hans) for the /showcase page
- Translate all UI elements including:
Testing Environment#
- Docusaurus version: 3.x
- Development server: localhost:3000
- Chinese version accessed via: localhost:3000/zh-Hans/showcase
- English version accessed via: localhost:3000/showcase
Attempted Approaches#
1. Standard Docusaurus i18n Translation Files#
The initial approach followed Docusaurus's official internationalization method:
- Creating translation JSON files in the appropriate directories:
- Using Docusaurus's built-in translation functions:
Results: Despite proper file placement and correct usage of the translation functions, translations were not applied. Debugging showed that while the locale was correctly detected as zh-Hans, the translation system returned English text.
2. Modifying Translation Keys#
We attempted to modify the translation keys to match different patterns:
- Using theme-prefixed keys: theme.showcase.filters.title
- Using plugin-specific directories: i18n/zh-Hans/docusaurus-theme-common/common.json
Results: These approaches didn't resolve the issue. The translation mechanism still failed to pick up the correct translations.
Successful Solution#
After several unsuccessful attempts with the built-in translation system, we implemented a direct conditional rendering approach:
const {i18n: {currentLocale}} = useDocusaurusContext();
{currentLocale === 'zh-Hans' ? '筛选' : translate({
id: 'theme.showcase.filters.title',
message: 'Filters'
})}This approach:
- Uses useDocusaurusContext to detect the current locale
- Renders Chinese text when the locale is zh-Hans
- Falls back to English text via the translation function for other locales
For component-specific translations like tags, we implemented a custom translation hook:
export function useTranslatedTags() {
const {i18n: {currentLocale}} = useDocusaurusContext();
return useMemo(() => {
// Function to get translation for a tag
const getTranslatedTag = (tagKey: TagType) => {
// Direct translations for Chinese locale
if (currentLocale === 'zh-Hans') {
const zhTranslations: Record<TagType, {label: string; description: string}> = {
'女性疾病': {
label: '女性疾病',
description: '专注于女性疾病的诊断、治疗和管理的公司'
},
// Additional translations...
};
return zhTranslations[tagKey];
}
// Default to English for other locales
return {
label: translate({
id: `femtech-tags.${tagKey}.label`,
message: TagDefaults[tagKey].defaultLabel,
}),
description: translate({
id: `femtech-tags.${tagKey}.description`,
message: TagDefaults[tagKey].defaultDescription,
}),
};
};
// Create Tags object with translations...
}, [currentLocale]);
}
File Structure Overview#
Final Effective Files#
src/
components/
ShowcaseFilters/index.tsx # Contains filters with i18n support
ShowcaseCards/index.tsx # Displays company cards with i18n
ClearAllButton/index.tsx # Filter clearing button with i18n
OperatorButton/index.tsx # Filter operator with i18n
ShowcaseCard/index.tsx # Individual company card with i18n
data/
femtech-companies.ts # Contains useTranslatedTags for tag i18n
pages/
showcase.tsx # English showcase page
i18n/
zh-Hans/
docusaurus-plugin-content-pages/
showcase.tsx # Chinese version of showcase page
Deleted Unused Files#
i18n/zh-Hans/docusaurus-theme-common.json # Unused translation file
i18n/en/docusaurus-theme-common.json # Unused translation file
i18n/zh-Hans/theme-common.json # Unused translation file
i18n/zh-Hans/theme.json # Unused translation file
i18n/zh-Hans/docusaurus-plugin-content-pages/showcase.json # Unused translation file
i18n/en/docusaurus-plugin-content-pages/showcase.json # Unused translation file
i18n/zh-Hans/docusaurus-theme-common/femtech-tags.json # Unused translation file
i18n/en/docusaurus-theme-common/femtech-tags.json # Unused translation file
Implementation Details#
1. Page Header and Description Translations#
English Version (src/pages/showcase.tsx):
function ShowcaseHeader() {
const {i18n: {currentLocale}} = useDocusaurusContext();
return (
<section className="margin-top--lg margin-bottom--lg text--center">
<Heading as="h1">
{translate({
id: 'header.title',
message: 'FemTech Companies Showcase',
})}
</Heading>
<p>
{translate({
id: 'header.description',
message: 'Directory of innovative companies in the women\'s health industry in China',
})}
</p>
</section>
);
}
Chinese Version (i18n/zh-Hans/docusaurus-plugin-content-pages/showcase.tsx):
function ShowcaseHeader() {
return (
<section className="margin-top--lg margin-bottom--lg text--center">
<Heading as="h1">
中国女性健康公司展示
</Heading>
<p>
中国女性健康领域的创新企业列表
</p>
</section>
);
}
2. UI Element Translations#
Filters Title:
function HeadingText() {
const {i18n: {currentLocale}} = useDocusaurusContext();
return (
<div className={styles.headingText}>
<Heading as="h2">
{currentLocale === 'zh-Hans' ? '筛选' : translate({
id: 'theme.showcase.filters.title',
message: 'Filters',
})}
</Heading>
<span>{companyCountPlural(filteredCompanies.length)}</span>
</div>
);
}
Clear Filters Button:
function ClearAllButton() {
const {i18n: {currentLocale}} = useDocusaurusContext();
return (
<button
type="button"
className="button button--sm button--outline button--danger"
onClick={clearAll}>
{currentLocale === 'zh-Hans' ? '清除筛选' : translate({
id: 'theme.showcase.filters.clearAll',
message: 'Clear filters',
})}
</button>
);
}
Operator Button:
function OperatorButton() {
const [operator, toggleOperator] = useOperator();
const {i18n: {currentLocale}} = useDocusaurusContext();
return (
<button
type="button"
className={`button button--sm button--${
operator === 'OR' ? 'secondary' : 'primary'
}`}
onClick={toggleOperator}>
{currentLocale === 'zh-Hans' ? `筛选条件: ${operator}` : translate(
{
id: 'theme.showcase.filters.operator',
message: 'Filter criteria: {operator}',
},
{
operator,
}
)}
</button>
);
}
Company Card Labels:
// In ShowcaseCard component
{company.founders && (
<div className={styles.showcaseCardDetail}>
<strong>
{currentLocale === 'zh-Hans' ? '创始人:' : translate({
id: 'theme.showcase.card.founder',
message: 'Founder:',
})}
</strong> {company.founders}
</div>
)}
{latestFunding && (
<div className={styles.showcaseCardDetail}>
<strong>
{currentLocale === 'zh-Hans' ? '融资:' : translate({
id: 'theme.showcase.card.funding',
message: 'Funding:',
})}
</strong> {latestFunding.round} ({latestFunding.date}) {latestFunding.amount && `- ${latestFunding.amount}`}
</div>
)}
Challenges Encountered#
1. Translation File Loading Issues#
Despite following Docusaurus documentation, the translation files weren't being properly loaded. The system would acknowledge the correct locale but wouldn't use the translated strings from JSON files.
2. Inconsistent Behavior#
Some translation keys would work with certain prefixes (theme. vs no prefix), while others wouldn't work regardless of the prefix used.
3. Debugging Difficulties#
It was challenging to debug the translation system as there was limited feedback on why translations weren't being applied. We added extensive debug logging to track:
- Current locale
- Translation keys being used
- Results of translation function calls
4. File Encoding#
At one point, a file displayed as garbled text, suggesting potential encoding issues with Chinese characters.
Lessons Learned#
- Docusaurus i18n Complexity: Docusaurus's internationalization system requires precise file structure and naming conventions that may not be immediately intuitive.
- Conditional Rendering as an Alternative: Direct conditional rendering based on locale can be more reliable than the built-in translation system for complex use cases.
- Component-Level vs. Page-Level i18n: For page-level content, using separate files (showcase.tsx in each language folder) works well, while component-level UI elements benefit from inline conditional rendering.
- Translation Key Specificity: The translation key format is critical - using the wrong format or missing prefixes can cause translations to fail silently.
- Testing on Real Devices: What works in development may behave differently in production, so thorough testing across environments is essential.
Best Practices and Recommendations#
- Choose the Right Approach:
- Simplify When Necessary:
- Consistent Implementation:
- Clean Up Unused Files:
- Proper Debugging:
- Consider User Experience:
By following these guidelines and learning from the experiences documented here, future developers should be able to implement internationalization more effectively in Docusaurus projects.