docs/localization.md
We want the official Bluesky app to be supported in as many languages as possible. If you want to help us translate the app, please get involved on Crowdin or open an issue on the Bluesky app repo on GitHub.
Much of the app is translated by community contributions. (We <3 our translators!) If you want to participate in the translation of the app, read this section.
Crowdin is our primary tool for managing translations. There are two roles:
All translations must be approved by proof-readers before they are accepted into the app.
You may contribute PRs separately from Crowdin, however we strongly recommend using Crowdin to avoid conflicts.
Please treat everyone with respect. Proof-readers are given final say on translations. Translators who frequently come into conflict with other translators, or who contribute noticably incorrect translations, will have their membership to the Crowdin project revoked.
You can request a new language be added to the project by clicking Request New Language on Crowdin or you can create a GitHub issue.
Please only request a new language when you are certain you will be able to contribute a substantive portion of translations for the language.
Install the Crowdin CLI. You will need to configure your API token to access the project.
Every night, a GitHub action will run yarn intl:extract to update the english messages.po file. This will be automatically synced with Crowdin. Crowdin should notify all subscribed users of new translations.
yarn intl:release to fetch all translation updates from Crowdin and extract all .po files so that they're synced with the latest code. Commit that.yarn intl:push to sync Crowdin with the state of the repo.You can run yarn intl:pull to pull the currently-approved translations from Crowdin.
When adding a new string, do it as follows:
// Before
import { Text } from "react-native";
<Text>Hello World</Text>
// After
import { Text } from "react-native";
import { Trans } from "@lingui/react/macro";
<Text><Trans>Hello World</Trans></Text>
The <Trans> macro will extract the string and add it to the catalog. It is not really a component, but a macro. Further reading here
However sometimes you will run into this case:
// Before
import { Text } from "react-native";
const text = "Hello World";
<Text accessibilityLabel="Label is here">{text}</Text>
In this case, you can use the useLingui() hook:
import { msg } from "@lingui/core/macro";
import { useLingui } from "@lingui/react";
const { _ } = useLingui();
return <Text accessibilityLabel={_(msg`Label is here`)}>{text}</Text>
NEW: the latest Lingui version introduced a new macro version of the useLingui hook which lets you do this:
import { useLingui } from "@lingui/react/macro";
const { t } = useLingui();
return <Text accessibilityLabel={t`Label is here`}>{text}</Text>
If you want to do this outside of a React component, you can use the global t macro instead (note: this won't react to changes if the locale is switched dynamically within the app):
import { t } from "@lingui/core/macro";
// not ideal - t only gets called once at module evaluation time
const text = t`Hello World`;
// however, this is suitable for strings that are ephemeral:
function sayHello() {
Toast.show(t`Hello World`); // Each time the toast shows, the current locale at that moment is used
}
We can then run yarn intl:extract to update the catalog in src/locale/locales/{locale}/messages.po. This will add the new string to the catalog.
We can then run yarn intl:compile to update the translation files in src/locale/locales/{locale}/messages.js. This will add the new string to the translation files.
The configuration for translations is defined in lingui.config.js
So the workflow is as follows:
yarn intl:extract command to generate message catalogsyarn intl:compile to create runtime catalogsThese pitfalls are memoization pitfalls that will cause the components to not re-render when the locale is changed -- causing stale translations to be shown.
import { msg } from "@lingui/core/macro";
import { i18n } from "@lingui/core";
const welcomeMessage = msg`Welcome!`;
// ❌ Bad! This code won't work
export function Welcome() {
const buggyWelcome = useMemo(() => {
return i18n._(welcomeMessage);
}, []);
return <div>{buggyWelcome}</div>;
}
// ❌ Bad! This code won't work either because the reference to i18n does not change
export function Welcome() {
const { i18n } = useLingui();
const buggyWelcome = useMemo(() => {
return i18n._(welcomeMessage);
}, [i18n]);
return <div>{buggyWelcome}</div>;
}
// ✅ Good! `useMemo` has i18n context in the dependency
export function Welcome() {
const linguiCtx = useLingui();
const welcome = useMemo(() => {
return linguiCtx.i18n._(welcomeMessage);
}, [linguiCtx]);
return <div>{welcome}</div>;
}
// 🤩 Better! `useMemo` consumes the `_` function from the Lingui context
export function Welcome() {
const { _ } = useLingui();
const welcome = useMemo(() => {
return _(welcomeMessage);
}, [_]);
return <div>{welcome}</div>;
}