Bluesky app fork with some witchin' additions 💫
witchsky.app
bluesky
fork
client
1# Internationalization
2
3We 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](https://bluesky.crowdin.com/bluesky-social) or open an issue on the [Bluesky app repo on GitHub](https://github.com/bluesky-social/social-app).
4
5## Tools
6
7- We use Crowdin to manage translations.
8 - Bluesky Crowdin: https://bluesky.crowdin.com/bluesky-social
9 - Introduction to Crowdin: https://support.crowdin.com/for-translators/
10- We use Lingui to implement translations. You can find the documentation [here](https://lingui.dev/).
11
12## Translators
13
14Much 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.
15
16### Using Crowdin
17
18[Crowdin](https://bluesky.crowdin.com/bluesky-social) is our primary tool for managing translations. There are two roles:
19
20- **Proof-readers**. Can create new translations and approve submitted translations.
21- **Translators**. Can create new translations.
22
23All translations must be approved by proof-readers before they are accepted into the app.
24
25### Using other platforms
26
27You may contribute PRs separately from Crowdin, however we strongly recommend using Crowdin to avoid conflicts.
28
29### Code of conduct on Crowdin
30
31Please 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.
32
33### Adding a new language
34
35You can request a new language be added to the project by clicking **Request New Language** on [Crowdin](https://bluesky.crowdin.com/bluesky-social) or you can create a [GitHub issue](https://github.com/bluesky-social/social-app/issues).
36
37Please only request a new language when you are certain you will be able to contribute a substantive portion of translations for the language.
38
39## Maintainers
40
41Install the [Crowdin CLI](https://crowdin.github.io/crowdin-cli/). You will need to [configure your API token](https://crowdin.github.io/crowdin-cli/configuration) to access the project.
42
43### English source-file sync with Crowdin
44
45Every 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.
46
47### Release process
48
491. Pull main and create a branch.
501. Run `yarn intl:pull` to fetch all translation updates from Crowdin. Commit.
511. Run `yarn intl:extract:all` to ensure all `.po` files are synced with the current state of the code. Commit.
521. Create a PR, ensure the translations all look correct, and merge.
531. If needed:
54 1. Merge all approved translation PRs (contributions from outside crowdin).
55 1. Run `yarn intl:push` to sync Crowdin with the state of the repo.
56
57### Testing the translations in Crowdin
58
59You can run `yarn intl:pull` to pull the currently-approved translations from Crowdin.
60
61## Developers
62
63### Adding new strings
64
65When adding a new string, do it as follows:
66```jsx
67// Before
68import { Text } from "react-native";
69
70<Text>Hello World</Text>
71```
72
73```jsx
74// After
75import { Text } from "react-native";
76import { Trans } from "@lingui/react/macro";
77
78<Text><Trans>Hello World</Trans></Text>
79```
80
81The `<Trans>` macro will extract the string and add it to the catalog. It is not really a component, but a macro. Further reading [here](https://lingui.dev/ref/macro.html)
82
83However sometimes you will run into this case:
84```jsx
85// Before
86import { Text } from "react-native";
87
88const text = "Hello World";
89<Text accessibilityLabel="Label is here">{text}</Text>
90```
91In this case, you can use the `useLingui()` hook:
92```jsx
93import { msg } from "@lingui/core/macro";
94import { useLingui } from "@lingui/react";
95
96const { _ } = useLingui();
97return <Text accessibilityLabel={_(msg`Label is here`)}>{text}</Text>
98```
99
100NEW: the latest Lingui version introduced a new macro version of the `useLingui` hook which lets you do this:
101
102```jsx
103import { useLingui } from "@lingui/react/macro";
104
105const { t } = useLingui();
106return <Text accessibilityLabel={t`Label is here`}>{text}</Text>
107```
108
109If 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):
110```jsx
111import { t } from "@lingui/core/macro";
112
113// not ideal - t only gets called once at module evaluation time
114const text = t`Hello World`;
115
116// however, this is suitable for strings that are ephemeral:
117function sayHello() {
118 Toast.show(t`Hello World`); // Each time the toast shows, the current locale at that moment is used
119}
120```
121
122We 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.
123We 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.
124The configuration for translations is defined in `lingui.config.js`
125
126So the workflow is as follows:
1271. Wrap messages in Trans macro
1282. Run `yarn intl:extract` command to generate message catalogs
1293. Translate message catalogs (send them to translators usually)
1304. Run `yarn intl:compile` to create runtime catalogs
1315. Load runtime catalog
1326. Enjoy translated app!
133
134### Common pitfalls
135
136These pitfalls are memoization pitfalls that will cause the components to not re-render when the locale is changed -- causing stale translations to be shown.
137
138```jsx
139import { msg } from "@lingui/core/macro";
140import { i18n } from "@lingui/core";
141
142const welcomeMessage = msg`Welcome!`;
143
144// ❌ Bad! This code won't work
145export function Welcome() {
146 const buggyWelcome = useMemo(() => {
147 return i18n._(welcomeMessage);
148 }, []);
149
150 return <div>{buggyWelcome}</div>;
151}
152
153// ❌ Bad! This code won't work either because the reference to i18n does not change
154export function Welcome() {
155 const { i18n } = useLingui();
156
157 const buggyWelcome = useMemo(() => {
158 return i18n._(welcomeMessage);
159 }, [i18n]);
160
161 return <div>{buggyWelcome}</div>;
162}
163
164// ✅ Good! `useMemo` has i18n context in the dependency
165export function Welcome() {
166 const linguiCtx = useLingui();
167
168 const welcome = useMemo(() => {
169 return linguiCtx.i18n._(welcomeMessage);
170 }, [linguiCtx]);
171
172 return <div>{welcome}</div>;
173}
174
175// 🤩 Better! `useMemo` consumes the `_` function from the Lingui context
176export function Welcome() {
177 const { _ } = useLingui();
178
179 const welcome = useMemo(() => {
180 return _(welcomeMessage);
181 }, [_]);
182
183 return <div>{welcome}</div>;
184}
185```