Skip to content

Contributing Translations

This guide provides step-by-step instructions for contributing a new language translation to Scrumlr. The application uses i18next for internationalization and currently supports English, German and French.

Before starting your translation contribution:

  1. Check existing languages: Review src/i18n/ to see currently supported languages
  2. Language code: Identify the appropriate ISO 639-1 language code for your language (e.g., fr for French, es for Spanish)
  3. Development setup: Ensure you have the development environment set up (see Setup and the Contributing Guide)

Scrumlr uses two main translation files per language:

  • src/i18n/{language_code}/translation.json - Main UI translations
  • src/i18n/{language_code}/templates.json - Board template translations

Adding a language touches more than these two files. There are four separate hardcoded language lists plus two directories of static assets.

Throughout this guide, replace es with your own language code.

Create the translation directory and files for your language:

Terminal window
mkdir src/i18n/es
cp src/i18n/en/translation.json src/i18n/es/translation.json
cp src/i18n/en/templates.json src/i18n/es/templates.json

Open src/i18n/es/translation.json and src/i18n/es/templates.json and translate all JSON values while keeping the keys unchanged.

Register the language in src/i18n/index.ts. This is the first of four language lists:

// Import your translation files
import translationEs from "./es/translation.json";
import templatesEs from "./es/templates.json";
export const resources = {
en: {
translation: translationEn,
templates: templatesEn,
},
// ...
// Add your language here
es: {
translation: translationEs,
templates: templatesEs,
},
};

Flag icons live in src/assets/flags/ and are named after the ISO 3166-1 alpha-2 country code, in uppercase — DE.svg, FR.svg, US.svg. Add yours the same way, e.g. src/assets/flags/ES.svg.

The existing flags are circular SVGs taken from HatScripts/circle-flags (see src/assets/flags/readme.md). Take yours from the same source so it matches the others visually.

There is no top-level assets/ directory — assets/* is a path alias for src/assets/*, which is why the import strings below look like they have no path.

5. Add the Language to the Settings Dropdown

Section titled “5. Add the Language to the Settings Dropdown”

Update src/components/SettingsDialog/Components/LanguageSettingsDropdown.tsx — the second language list:

// Import the flag icon. Note the `?react` suffix: it is what turns the SVG into a component.
import Spanish from "assets/flags/ES.svg?react";
// Add to the languages array
const languages = [
{icon: English, text: t("Language.english"), callback: () => changeLanguage("en"), code: "en"},
{icon: German, text: t("Language.german"), callback: () => changeLanguage("de"), code: "de"},
{icon: French, text: t("Language.french"), callback: () => changeLanguage("fr"), code: "fr"},
// Add your language
{icon: Spanish, text: t("Language.spanish"), callback: () => changeLanguage("es"), code: "es"},
];

6. Add the Language to the Homepage Picker

Section titled “6. Add the Language to the Homepage Picker”

The landing page has its own flag list, separate from the settings dialog. Update src/routes/Homepage/Homepage.tsx:

import Spanish from "assets/flags/ES.svg?react";
<li>
<LegacyButton leftIcon={<Spanish />} className="homepage__language" hideLabel onClick={changeLanguage("es")}>
Español
</LegacyButton>
</li>

Note that the label here is the language’s own name, written out literally rather than translated.

7. Add the Language to the Test i18n Instance

Section titled “7. Add the Language to the Test i18n Instance”

src/i18nTest.ts is a second, test-only i18next instance with a third hardcoded list. It uses the translation namespace only:

import translationEs from "./i18n/es/translation.json";
export const resources = {
en: {translation: translationEn},
de: {translation: translationDe},
fr: {translation: translationFr},
es: {translation: translationEs},
};

Skip this and tests that assert on translated text will render raw keys, with no obvious explanation.

Add the language name to the Language key of every translation file, including the new one:

e.g. src/i18n/en/translation.json:

{
"Language": {
"english": "English",
"german": "German",
"french": "French",
"spanish": "Spanish"
}
}

The emoji picker loads its dataset per language from public/emoji-data/<language_code>.json. EmojiPicker.tsx builds the URL from i18n.resolvedLanguage, so a language without a matching file gets a 404 and a broken picker.

Generate the file with:

Terminal window
node scripts/generateEmojiData.mjs

The script writes to scripts/out/ (gitignored); copy the result into public/emoji-data/.

  1. Run the key parity test:

    Terminal window
    yarn test --run src/__tests__/locales.test.ts

    This compares your translation.json against the English one. Be aware of its limits: it only checks the top two levels of keys, and it does not look at templates.json at all. A green run means you have not missed a whole section and not that the file is complete.

  2. Start the development server:

    Terminal window
    yarn start
  3. Test language switching:

    • Switch from the homepage flag buttons, and from Settings → Appearance
    • Verify all text displays correctly in both places
  4. Test completeness:

    • Navigate through all application features
    • Check that no English text remains
    • Open the emoji picker and a board template to check the templates namespace and the emoji data
    • Verify special characters display correctly

Far more common than adding a language: you are building a feature and need a new string.

  1. Add the key to src/i18n/en/translation.json, under an object named after your component ("Note": {"showMore": "Show more"}).

  2. Add the same key to de/translation.json and fr/translation.json. A machine translation is acceptable — say so in the pull request and a native speaker can correct it later. Leaving the key out is not, because src/__tests__/locales.test.ts will fail.

  3. Use it in the component:

    const {t} = useTranslation();
    // ...
    {t("Note.showMore")}

    With interpolation:

    {t("Note.userImageAlt", {user: participant.user.name})}

Two special cases:

  • Board templates live in the templates namespace: useTranslation(["translation", "templates"]).
  • retryable error keys must go under the Error object in src/i18n/en/translation.json. The key is typed, so a thunk using a missing one will not compile — see State & Realtime.
  1. Maintain context: Understand the UI context where text appears
  2. Consistent terminology: Use consistent terms throughout your translation
  3. Length considerations: Consider text length differences that might affect UI layout

Some strings use i18next pluralization syntax:

{
"column_one": "{{count}} Column",
"column_other": "{{count}} Columns"
}

Follow your language’s pluralization rules and i18next documentation for proper implementation.

  • Use UTF-8 encoding for all translation files
  • Test special characters (accents, umlauts, etc.) thoroughly
  • Ensure proper display across different browsers and devices
  1. Create a pull request with the title format: feat: add {Language Name} translation support

  2. Include in your PR description:

    • Language code used
    • Any specific notes about your translation choices
    • Confirmation that you’ve tested the complete UI
  3. Be prepared for review:

    • Maintainers may ask native speakers to review samples
    • Be open to feedback and suggestions

Once your translation is merged:

  • Monitor updates: Watch for new strings added to the English version
  • Update accordingly: Contribute updates when new features are added
  • Report issues: If you notice translation problems, please report them

If you need assistance during the translation process:

  • GitHub Discussions: Ask questions in the Q&A section
  • Issues: Report technical problems with the translation system
  • Community: Connect with other translators and maintainers

🌍 Our Amazing Translation Contributors 🎉

Section titled “🌍 Our Amazing Translation Contributors 🎉”

We want to give a huge shoutout to all the incredible people who have made Scrumlr accessible to users around the world! 🙌

  • Core team contributors 💪
  • Loule95450 - Thank you for adding French support! 🥐✨

Join our community and help make Scrumlr available in your language! Every contribution, no matter how small, makes a difference. 💪


Thank you for contributing to make Scrumlr accessible to more users worldwide! Your efforts help break down language barriers and bring the power of collaborative retrospectives to teams everywhere.

Together, we’re building something amazing! 🚀✨