Merged in chore/update-readmes (pull request #2751)
chore: Update README * Update readme Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -15,282 +15,3 @@ In order to not prop drill that all the way from a page we use React's `cache` i
|
||||
For this to work we must set the language with `setLang` on the topmost layout
|
||||
|
||||
This was inspired by [server-only-context](https://github.com/manvalls/server-only-context)
|
||||
|
||||
## Translations a.k.a. UI labels a.k.a. Lokalise
|
||||
|
||||
### Quickstart: How to use react-intl in the codebase
|
||||
|
||||
> For more information read about [the workflow below](#markdown-header-the-workflow).
|
||||
|
||||
- **Do not destructure `formatMessage` from either `getIntl()` or `useIntl()`.**
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const { formatMessage } = useIntl()
|
||||
const message = formatMessage(...)
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
```typescript
|
||||
const intl = useIntl()
|
||||
const message = intl.formatMessage(...)
|
||||
```
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const { formatMessage } = await getIntl()
|
||||
const message = formatMessage(...)
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
```typescript
|
||||
const intl = await getIntl()
|
||||
const message = intl.formatMessage(...)
|
||||
```
|
||||
|
||||
- **Do not pass variables as defaultMessage.**
|
||||
|
||||
The `defaultMessage` needs to be a literal string so that the tooling can properly find and extract defined messages. Do not use template strings with variable interpolation either.
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const data = await getSomeData()
|
||||
...
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: data.type,
|
||||
})
|
||||
...
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: `Certification: ${data.type}`,
|
||||
})
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
This is a hard one to give a general solution/rule for, but in essence it should use either use a "switch approach" or `defineMessage/defineMessages` ([docs](https://formatjs.github.io/docs/react-intl/api/#definemessagesdefinemessage)) in some way.
|
||||
|
||||
The most common reason for this scenario is that the data contains one or several words/sentences we want translated.
|
||||
|
||||
The preferred solution is to use a "switch approach" (or something equivalent), checking some property of the entity to decide what message to use.
|
||||
|
||||
- It is explicit about what cases are supported: this helps finding bugs by lowering complexity by making it easier to find where a string is used.
|
||||
- The declaration is coupled with the usage: keeping our messages up to date and clutter free over time.
|
||||
- The formatjs eslint plugin helps enforce proper usage.
|
||||
- TypeScript will force us to keep the list up to date with the typings of `data.type`. Preferably ALL data points of this manner should be an enum or an array of strings `as const` (or equivalent) for the best TS support here.
|
||||
|
||||
```typescript
|
||||
...
|
||||
const data = await getSomeData()
|
||||
...
|
||||
let message = intl.formatMessage({defaultMessage: 'N/A'})) // or some other default/"no match" message
|
||||
switch (data.type) {
|
||||
case "Restaurant":
|
||||
message = intl.formatMessage({defaultMessage: "Restaurant"})
|
||||
break;
|
||||
case "Bar":
|
||||
message = intl.formatMessage({defaultMessage: "Bar"})
|
||||
break;
|
||||
case "Pool":
|
||||
message = intl.formatMessage({defaultMessage: "Pool"})
|
||||
break;
|
||||
case "Some certification":
|
||||
message = intl.formatMessage({defaultMessage: "Certification: {name}"}, { name: "Some certification"})
|
||||
break;
|
||||
default:
|
||||
// TS will throw if it reaches here if typings for `data.type` are properly defined and the above cases are exhaustive.
|
||||
// This will help us keep messages up to date.
|
||||
const type: never = data.type
|
||||
console.warn(`Unsupported type given: ${type}`)
|
||||
}
|
||||
```
|
||||
|
||||
If the above is not possible the escape hatch is using something like the following. Avoid using this because:
|
||||
|
||||
- This decouples the message declaration from the message consumption: causing stale messages to linger around and clutter up the codebase.
|
||||
- It makes it a lot harder to find where a string is being used in the codebase.
|
||||
- The formatjs eslint plugin is unable to enforce placeholders: this decreases confidence in our messages and potentially hiding bugs.
|
||||
|
||||
```typescript
|
||||
import { defineMessages } from "react-intl"
|
||||
...
|
||||
const data = await getSomeData()
|
||||
...
|
||||
const restaurantMessage = defineMessage({
|
||||
defaultMessage: "Restaurant",
|
||||
})
|
||||
const barMessage = defineMessage({
|
||||
defaultMessage: "Bar",
|
||||
})
|
||||
const poolMessage = defineMessage({
|
||||
defaultMessage: "Pool",
|
||||
})
|
||||
// OR
|
||||
const messages = defineMessages({
|
||||
restaurant: {
|
||||
defaultMessage: "Restaurant",
|
||||
},
|
||||
bar: {
|
||||
defaultMessage: "Bar",
|
||||
},
|
||||
pool: {
|
||||
defaultMessage: "Pool",
|
||||
}
|
||||
})
|
||||
...
|
||||
return (
|
||||
<p>{intl.formatMessage(messages.restaurant)}</p> // or .bar or .pool, etc.
|
||||
)
|
||||
...
|
||||
|
||||
// Since calls to defineMessage/defineMessages get their messages extracted,
|
||||
// technically you can do the following instead of accessing the key like above.
|
||||
// But it is not encouraged as this decoupling leads to had to track usage and decay over time:
|
||||
const message = intl.formatMessage({
|
||||
// eslint-disable-next-line formatjs/enforce-default-message
|
||||
defaultMessage: data.type, // data.type === "Restaurant" | "Bar" | "Pool"
|
||||
})
|
||||
```
|
||||
|
||||
- **Do not use defaultMessage key as an alias.**
|
||||
|
||||
The `defaultMessage` should be the actual message you want to display (and send to Lokalise). It must be written in American/US English.
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: "some.alias",
|
||||
})
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
```typescript
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: "The real message is here in US English",
|
||||
})
|
||||
```
|
||||
|
||||
- **Do not use conditionals when defining messages.**
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: someVariable ? "Variable is truthy" : "Variables i falsey",
|
||||
})
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
```typescript
|
||||
const truthyMessage = intl.formatMessage({
|
||||
defaultMessage: "Variable is truthy",
|
||||
})
|
||||
const falseyMessage = intl.formatMessage({
|
||||
defaultMessage: "Variable is falsey",
|
||||
})
|
||||
const message = someVariable ? truthyMessage : falseyMessage
|
||||
```
|
||||
|
||||
- **Avoid using ICU special words as placeholder names**
|
||||
|
||||
Some words have meaning in ICU when dealing with placeholders and such. To avoid bugs and confusion do not use them as placehodler names.
|
||||
|
||||
Avoid (list is not exhaustive):
|
||||
|
||||
- `{number}`
|
||||
- `{integer}`
|
||||
- `{plural}`
|
||||
- `{date}`
|
||||
- `{time}`
|
||||
- `{select}`
|
||||
- `{choice}`
|
||||
- `{other}`
|
||||
|
||||
Also do not use the current variable name in scope as the placeholder name (unless the variable is named following the below rules). It will confuse translators.
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const number = getValueSomeWay()
|
||||
...
|
||||
const message = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "The number is {number}",
|
||||
},
|
||||
{
|
||||
number,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
nor this
|
||||
|
||||
```typescript
|
||||
const goodNameForVarButNotForPlaceholder = getValueSomeWay()
|
||||
...
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: "The number is {goodNameForVarButNotForPlaceholder}",
|
||||
}, {
|
||||
goodNameForVarButNotForPlaceholder
|
||||
})
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
Prefer a placeholder name that gives context to the message when reading it without the context of the code
|
||||
|
||||
```typescript
|
||||
const goodNameForVarButNotForPlaceholder = getValueSomeWay()
|
||||
...
|
||||
const message = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "The number is {membershipNumber}",
|
||||
},
|
||||
{
|
||||
membershipNumber: goodNameForVarButNotForPlaceholder,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
or if context is hard to give, use generic words like `value`, `count`, `amount`, etc.
|
||||
|
||||
```typescript
|
||||
const number = getValueSomeWay()
|
||||
...
|
||||
const message = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "The number is {value}",
|
||||
},
|
||||
{
|
||||
value: number,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
- Do not give id to messages.
|
||||
|
||||
The eslint plugin will automatically fix this (removes the id on save/fix).
|
||||
|
||||
❌ Do not do this:
|
||||
|
||||
```typescript
|
||||
const message = intl.formatMessage({
|
||||
id: "some-id",
|
||||
defaultMessage: "This is a message",
|
||||
})
|
||||
```
|
||||
|
||||
✅ instead do this:
|
||||
|
||||
```typescript
|
||||
const message = intl.formatMessage({
|
||||
defaultMessage: "This is a message",
|
||||
})
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user