i18n: Use full sentences for translation keys instead of fragments
Description
The current i18n implementation composes sentences from multiple, separate translation keys. This is an internationalization (i18n) anti-pattern that prevents accurate and natural-sounding translations for languages with different grammatical structures.
Current Implementation Examples
The following code snippets show how sentence fragments are currently being concatenated directly in the UI components:
1. Hero Section Titles (/components/landing/Hero.tsx)
The main title and subtitle are constructed from different keys.
<h1 ...>
{t('hero.title_part1')}{' '}
<span ...>{t('hero.title_highlight')}</span>.
</h1>
<h2 ...>
{t('hero.subtitle_part1')}{' '}
<a ...>
{t('hero.subtitle_link')}
</a>{' '}
{t('hero.subtitle_part2')}
</h2>
2. Expense Details (/components/expense/ExpenseDetails.tsx)
A sentence is formed by injecting a translated preposition between two variables.
{toUIString(e.amount)} {t('ui.expense.to')} {displayName(userDetails.data, userId)}
Why This Is a Problem
- Incorrect Grammar: It hardcodes the English sentence structure, which will break translations for languages with different word orders.
-
Ambiguous Translations: It forces a single translation for words that require context. For example, the prepositions
"for"and"to"(ui.expense.for,ui.expense.to) can have multiple, different translations in other languages depending on the sentence's meaning (e.g., purpose, direction, recipient). In some cases, they might even translate to the same word. Without the full sentence, a translator cannot choose the correct form.
**Expected Behavior **
Translation keys should contain complete sentences, using interpolation for dynamic values. This gives translators the full context they need to create a correct and natural translation.
Example Refactoring:
Before:
"ui": {
...
"expense": {
"for": "for",
...
"to": "to",
...
}
...
}
{toUIString(e.amount)} {t('ui.expense.to')} {displayName(userDetails.data, userId)}
After:
"ui": {
"expense": {
"amount_owed_to_user": "{{amount}} to {{name}}"
}
}
// component.jsx
t('ui.expense.amount_owed_to_user', {
amount: toUIString(e.amount),
name: displayName(userDetails.data, userId)
})
This approach ensures the entire phrase is translated as a single, coherent unit and provides context in which the string is used, preventing just "to" being used with different screens/contexts, for instance.
For reference:
- https://www.i18next.com/translation-function/interpolation
- https://react.i18next.com/latest/trans-component
Since I leave i18n support to community, I am not going to prioritize any work on it, but will accept PRs.