Skip to main content
Documenso uses Lingui for internationalization (i18n) and manages translations through Crowdin. This guide explains how to contribute translations and work with the i18n system.

Supported Languages

Documenso currently supports the following languages:

German

de

English

en (source)

French

fr

Spanish

es

Italian

it

Dutch

nl

Polish

pl

Portuguese (Brazil)

pt-BR

Japanese

ja

Korean

ko

Chinese

zh

Translation Workflow

For Translators

If you want to translate Documenso into a new language or improve existing translations:
1

Join Crowdin

Visit the Documenso Crowdin project and request to join.
2

Select a language

Choose the language you want to translate or a new language to add.
3

Translate strings

Use Crowdin’s interface to translate strings. Crowdin provides:
  • Context for each string
  • Translation suggestions
  • Translation memory
  • Quality checks
4

Submit translations

Your translations will be reviewed and automatically synced to the repository.
Translations are automatically synced from Crowdin to the GitHub repository, so you don’t need to create pull requests for translations.

For Developers

If you’re adding new features or content that needs translation:
1

Use Lingui macros

Wrap all user-facing strings with Lingui macros.
2

Extract strings

Run the extraction command to update translation catalogs.
3

Commit the changes

Commit the updated catalog files.
4

Translators notified

Crowdin will automatically detect new strings for translation.

Using Lingui in Code

JSX Translations

For translating strings in JSX, use the <Trans> component:
import { Trans } from '@lingui/react/macro';

export const WelcomeMessage = () => {
  return (
    <div>
      <h1>
        <Trans>Welcome to Documenso</Trans>
      </h1>
      <p>
        <Trans>Sign documents with ease</Trans>
      </p>
    </div>
  );
};

TypeScript Translations

For translating strings in TypeScript code, use the t macro:
import { t } from '@lingui/react/macro';
import { useLingui } from '@lingui/react';

export const sendEmail = () => {
  const { _ } = useLingui();
  
  const subject = t`Document ready for signing`;
  const message = t`Please review and sign the document`;
  
  // Use the translations
  emailService.send({
    subject: _(subject),
    body: _(message),
  });
};

Translations with Variables

You can include variables in translations:
import { Trans } from '@lingui/react/macro';

const DocumentTitle = ({ name, count }: Props) => {
  return (
    <h2>
      <Trans>
        {name} has {count} documents
      </Trans>
    </h2>
  );
};

Pluralization

Handle plurals correctly:
import { Trans, Plural } from '@lingui/react/macro';

const DocumentCount = ({ count }: Props) => {
  return (
    <p>
      <Plural
        value={count}
        one="# document"
        other="# documents"
      />
    </p>
  );
};

Translation File Structure

Translations are stored in PO (Portable Object) files:
packages/lib/translations/
├── de/
│   └── web.po
├── en/
│   └── web.po (source)
├── es/
│   └── web.po
├── fr/
│   └── web.po
└── ...

PO File Format

PO files contain translation entries:
#: apps/remix/app/components/auth/signin-form.tsx
msgid "Sign in to your account"
msgstr "Connectez-vous à votre compte"

#: apps/remix/app/components/auth/signin-form.tsx
msgid "Email address"
msgstr "Adresse e-mail"

Working with Translation Commands

Extract Translations

Extract new strings from the codebase:
npm run translate:extract
This command:
  • Scans all source files for Lingui macros
  • Updates the English source catalog (en/web.po)
  • Cleans up unused translations

Compile Translations

Compile PO files to optimized JavaScript:
npm run translate:compile
This command:
  • Converts PO files to JS modules
  • Optimizes for production use
  • Must be run before building the app

Full Translation Update

Run both extract and compile:
npm run translate
Always run npm run translate:compile before building the application or starting the development server.

Lingui Configuration

The translation system is configured in lingui.config.ts:
import { defineConfig } from '@lingui/cli';
import { formatter } from '@lingui/format-po';
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/locales';

const config = {
  sourceLocale: 'en',
  locales: ['de', 'en', 'es', 'fr', 'it', 'nl', 'pl', 'pt-BR', 'ja', 'ko', 'zh'],
  catalogs: [
    {
      path: '<rootDir>/packages/lib/translations/{locale}/web',
      include: ['apps/remix/app', 'packages/ui', 'packages/lib', 'packages/email'],
      exclude: ['**/node_modules/**'],
    },
  ],
  format: formatter({ lineNumbers: false }),
};

export default config;

Crowdin Integration

Crowdin automatically syncs translations with the repository:

Crowdin Configuration

The crowdin.yml file defines how translations sync:
files:
  - source: packages/lib/translations/en/web.po
    translation: packages/lib/translations/%two_letters_code%/%original_file_name%

How It Works

  1. Developer adds new strings → Strings extracted to en/web.po
  2. PR merged to main → Crowdin detects new strings
  3. Translators translate → Strings translated in Crowdin
  4. Automatic sync → Crowdin creates PR with translations
  5. Maintainer merges → Translations available in app

Best Practices

Writing Translatable Strings

Shorter strings are easier to translate and fit better in different layouts.
// ✅ Good
<Trans>Sign document</Trans>

// ❌ Avoid
<Trans>Please click here to sign the document that has been sent to you</Trans>
Don’t concatenate strings as word order varies by language.
// ✅ Good
<Trans>Welcome, {name}</Trans>

// ❌ Avoid
<Trans>Welcome</Trans> + ', ' + name
Add comments to help translators understand context.
// i18n: This appears on the document signing page
<Trans>Sign here</Trans>
Keep dynamic values outside the translatable string.
// ✅ Good
<Trans>Signed by {count} people</Trans>

// ❌ Avoid
<Trans>Signed by</Trans> {count} <Trans>people</Trans>

Testing Translations

  1. Test with pseudo-localization to catch layout issues:
    LOCALE=en npm run dev
    
  2. Test actual translations by switching languages in the app
  3. Check for truncation - make sure UI handles longer translations
  4. Verify placeholders - ensure variables display correctly

Adding a New Language

To add support for a new language:
1

Update supported languages

Add the language code to packages/lib/constants/locales.ts:
export const SUPPORTED_LANGUAGE_CODES = [
  'de',
  'en',
  'fr',
  // ... other languages
  'new-lang', // Add your language code
] as const;
2

Update Lingui config

Add the language to lingui.config.ts:
locales: ['de', 'en', 'fr', /* ... */ 'new-lang'],
3

Extract translations

Run the extract command to create the new catalog:
npm run translate:extract
4

Add to Crowdin

Request the language to be added to the Crowdin project.
5

Start translating

Begin translating strings in Crowdin for the new language.

Troubleshooting

Missing Translations

Problem: Strings appear in English instead of the selected language Solution:
  1. Check if translations exist in the PO file
  2. Run npm run translate:compile
  3. Restart the development server

Outdated Catalogs

Problem: New strings not appearing in Crowdin Solution:
  1. Run npm run translate:extract
  2. Commit and push the updated en/web.po file
  3. Wait for Crowdin to sync (usually automatic)

Build Errors

Problem: Build fails with translation errors Solution:
# Clean and recompile translations
npm run translate:compile
npm run build

Resources

Lingui Documentation

Official Lingui documentation

Crowdin Project

Join the translation team

Discord Community

Get help from the community

Translation Files

View translation files on GitHub

Need Help?

If you have questions about translations: