Localisation
Localisation support is provided by the next-intl library.
For information on how this is setup see their getting started docs.
Localised routing has been setup inline with the official example provided here.
Translation
There are multiple translation source files, which one a translation lives in is dependant on where it’s used.
If a translation is used across both web and mobile apps it will belong in the shared workspace.
- If it is common across products then place it in shared/messages/en-GB.json. An example would be ‘Save’ or ‘Cancel’.
- If it is specific to a single product but used in both web and mobile apps then place it in shared/messages/product/en-GB.json. An example would be ‘A fresh take on dog food’ would live in shared/messages/dogs/en-GB.json.
If a translation is specific to a single product and platform it belongs in the app workspace for that product.
- A translation specific to the cats workspace would belong in apps/cats/app/messages/en-GB.json
Client components
import { useTranslations } from "next-intl"
const t = useTranslations()Server components
import { getTranslations } from "next-intl/server"
const t = await getTranslations("your-namespace")Layouts with translation
When building layouts it is important to remember that the copy being placed into an element will vary in length. In languages such as German for example the length of a word can easily double when translated.
Keep this in mind and be sure to compensate for variable word lengths.
The currently selected locale can be accessed via the useLocale hook provided by next-intl
import { useLocale } from "next-intl"
const locale = useLocale()Typescript integration with translation files
Our translation files are typed providing autocompletion and type safety. See the next-intl documentation for more information.
Routes
Routes are also localised, localisations are added to the translation file and then added via the navigation handler.
www.butternutbox.com/en-GB/loginwww.butternutbox.de/de-DE/anmeldungDefault localisation values
We can define certain values globally to reduce redundancy and improve consistency.
These values are accessible through any translation files without needing to be explicitly passed through.
For example {brand} can be used to refer to the brand name of the product anywhere in our translation files.
"message": "{brand} is the best petfood on the market!"In the above example we can adapt our sites easily for markets where we have multiple different brands for the same product ie. Butternut Box and PsiBuffet. See the next-intl documentation for more information.
Time and dates
Formatting dates won’t require passing through the locale. As our pages throughout the app are
wrapped with the <NextIntlClientProvider /> HOC, next-intl is aware of the locale at all times.
Basic Usage
import { useFormatter } from "next-intl"
const format = useFormatter()
return (
<Typography>
{format.dateTime(new Date(), {dateStyle: "full"})}
</Typography>
)Be aware that translation keys over 7 levels deep will silently fail ie “1.2.3.4.5.6.7.8”
Read More
For more information on how to format dates and time see the next-intl docs.
For more usage examples refer to Storybook.
Currency
Similarly to date/time formatting, number formatting won’t require passing through the locale.
Basic Usage
import { useFormatter } from "next-intl"
const format = useFormatter()
return (
<Typography>
{format.number(1000, {
style: "currency",
currency: Currencies.GBP
})}
</Typography>
)Read More
For more information on how to format Numbers see the next-intl docs.
For more usage examples refer to Storybook.
Grammar
Several common grammar rules that tend to be repeated a lot across our copy are available for use from our shared translation file.
"grammar": {
"adjectives": {
"possessive": "{gender, select, female {her} male {his} other {their}}"
},
"pronouns": {
"possessive": "{gender, select, female {hers} male {his} other {theirs}}",
"singular": "{gender, select, female {she} male {he} other {they}}",
"contracted": "{gender, select, female {she's} male {he's} other {they're}}",
"objective": "{gender, select, female {her} male {him} other {them}}",
"personal": "{gender, select, female {her} male {his} other {theirs}}"
}
},This allows engineers to avoid repetition and enforce the correct grammar when translating copy.
"tip": "Try feeding {objectivePronoun} after playtime or outdoor adventures when {possessiveAdjective} appetite is up. Think: Hunt–Fetch–Kill–Eat (just like {possessiveAdjective} wild ancestors).",const objectivePronoun = useMemo(
() =>
t("grammar.pronouns.objective", {
gender: animals.length > 1 ? false : animals[0]?.gender
}),
[animals, t]
)
const possessiveAdjective = useMemo(
() =>
t("grammar.adjective.possessive", {
gender: animals.length > 1 ? false : animals[0]?.gender
}),
[animals, t]
)
{
t(`tip`, {
objectivePronoun,
possessiveAdjective
})
}Ideally these grammar rules should be abstracted to a hook/provider so they only need to be defined once and can be accessed easily - see the cats usePlan hook for an example.