Implementing Internationalization in a Docusaurus Website

May 14, 2025 (1y ago) · 6 min read

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#

  1. Initial Requirements
  2. Testing Environment
  3. Attempted Approaches
  4. Successful Solution
  5. File Structure Overview
  6. Implementation Details
  7. Challenges Encountered
  8. Lessons Learned
  9. 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:

  1. Creating translation JSON files in the appropriate directories:
  2. 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:

  1. Using theme-prefixed keys: theme.showcase.filters.title
  2. 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:

  1. Uses useDocusaurusContext to detect the current locale
  2. Renders Chinese text when the locale is zh-Hans
  3. 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#

  1. Docusaurus i18n Complexity: Docusaurus's internationalization system requires precise file structure and naming conventions that may not be immediately intuitive.
  2. 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.
  3. 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.
  4. Translation Key Specificity: The translation key format is critical - using the wrong format or missing prefixes can cause translations to fail silently.
  5. Testing on Real Devices: What works in development may behave differently in production, so thorough testing across environments is essential.

Best Practices and Recommendations#

  1. Choose the Right Approach:
  2. Simplify When Necessary:
  3. Consistent Implementation:
  4. Clean Up Unused Files:
  5. Proper Debugging:
  6. 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.