Skip to main content

Internationalization

Are you interested in bringing Actual to a broader audience by translating and localizing the app into new languages? Great! This guide will show you how to get started. Adding translations can be a great first step into contributing to open-source software.

Translating existing strings

Actual uses Weblate to manage all translations; you'll need to create a Weblate account with two-factor authentication enabled in order to contribute. In particular, we use Weblate's "suggestions" system to maintain high-quality translations. Please join the community Discord and visit the #project-translate channel for translation-related discussion!

We currently only include languages in the app once at least 50% of strings have been translated. We may revise this policy as needed.

Pull Requests

We do not accept GitHub Pull Requests modifying translation files; any PRs in the translations repository will be automatically closed.

Suggesting a new translation

To suggest a translation for a language you are familiar with, just find a string you'd like to translate, fill in a translation, and hit "Suggest." This will then give others the opportunity to vote on the most accurate translation. Check out the Weblate docs for more details.

Please only fill in translations that you are confident in, and be sure to include the same formatting markers as the English text (example: <1>English</1> -> <1>Anglais</1>).

Voting on existing suggestions

To help maintain a high bar for translation quality, suggested translations are not accepted until they receive at least two up-votes.

You can help identify high-quality translations by voting on them! Click on "Strings with suggestions" from a language page, and use the thumbs-up or thumbs-down icons to vote. If you click thumbs-down, it's always helpful to provide a reason so that the original translator can improve.

If you see a mistranslation or malicious contribution, please flag it in the #project-translate channel on Discord so an admin can review it.

Marking new strings for translation

Developing translated software requires that every text shown to the user passes through a translation function. Actual uses i18next to achieve this goal.

Strings in Actual are translated by their natural English language version. The project does not use artificial keys to select the appropriate translated text from the message catalog.

Using natural language keys

Consider the text "Welcome to Actual Budget". When marking strings for translation using natural language, this string also becomes the key used to translate it into other languages. This is in contrast to a commonly used alternative of using artificial keys, which would look like "text_welcome". Actual Budget does not use these keys.

When preparing your translatable strings, please use complete units to translate. If there is a cohesive paragraph with multiple sentences, mark the entire paragraph as one translatable string. Do not split it into multiple texts.

The translation framework provides two primary methods for translation:

t() Function

The t() function is the simplest form of translation, it accepts the text to be translated and an optional object with interpolation variables. We're using the i18next singleton instance. You can use the hook within React components to get a reference to it.

import { useTranslation } from 'react-i18next';

function MyComponent() {
const { t } = useTranslation();

return <button aria-label={t('Hello World')} />;
}

At runtime, it will select the translation from the currently active locale. You can also add variables to interpolate:

const withPlaceholder = t('Welcome home, {{name}}!', { name: 'Anita' });

For pluralization, please use the plural form for the default text. The placeholder variable must be named count:

const manyItems = t('You selected {{count}} items', { count: 4 });

i18next will take care of proper pluralization rules in the currently active language and provide multiple different forms if required.

<Trans> Component

When translating text with simple HTML tags or other React components, we ask that you use the <Trans> component.

import { Trans } from 'react-i18next';

function MyComponent() {
return (
<Trans>
Click <Link href={url}>here</Link> to become a millionaire.
</Trans>
);
}

Interpolation with <Trans>

Handling placeholder variables and pluralization within the <Trans> React component is handled slightly differently. For more details, please refer to i18next-react's documentation.

import { Trans } from 'react-i18next';

function MyComponent() {
const person = { name: 'Pedro' };
// You can use an object literal within <Trans>!
return <Trans>Your name is {{ name: person.name }}.</Trans>;
// This will result in the translation string:
// "Your name is {{name}}."
}