Skip to content

Latest commit

 

History

History
156 lines (124 loc) · 7.65 KB

File metadata and controls

156 lines (124 loc) · 7.65 KB

Internationalization Philosophy

This application is built with Internationalization (I18n) / Localization (L10n) support to provide a consistent experience across different languages and regions.

Related Philosophies

Terminology

  • I18n - Internationalization, the process of designing software to support multiple languages
  • L10n - Localization, the process of adapting software for specific languages and regions
  • Translation Keys - Unique identifiers for translatable strings
  • Locale - A specific language and region combination (e.g., en-US, es-ES)

Rules

- All user-facing content MUST be localized

The following types of data MUST always be localized when presented to the user (including accessibility texts that are not rendered):

- Components MUST use the useLocalize hook for translations

In most cases, you will need to localize data used in a component. Use the useLocalize hook, which abstracts most of the logic you need (primarily subscribing to the NVP_PREFERRED_LOCALE Onyx key).

- Translations MUST be organized by feature and stored in language files

All translations are stored in language files in src/languages. Translations SHOULD be grouped by their pages/components for better organization.

- Common phrases SHOULD be shared when used in multiple places

A common rule of thumb is to move a common word/phrase to be shared when it's used in 3 or more places.

- Complex translation strings MUST NOT be split up for formatting or value injection

Always prefer to use arrow functions and/or HTML to produce rich text in translation files. For example, if you need to generate the text User has sent $20.00 to you on Oct 25th at 10:05am, add just one key to the translation file and use the arrow function version:

nameOfTheKey: ({amount, dateTime}) => `User has sent <strong>${amount}</strong> to you on <a>${datetime}</a>`,

This is because the order of phrases might vary from one language to another, and LLMs will be able to produce better translations will the full context of the phrase. If rich formatting is needed, use HTML in the string and render it with react-native-render-html.

- String concatenation SHOULD NOT be used for translations

Always prefer whole phrases over string concatenation, even if the result is more verbose:

// BAD
{
    receiptRequired: ({formattedLimit, category}: ViolationsReceiptRequiredParams) => {
        let message = 'Receipt required';
        if (formattedLimit ?? category) {
            message += ' over';
            if (formattedLimit) {
                message += ` ${formattedLimit}`;
            }
            if (category) {
                message += ' category limit';
            }
        }
        return message;
    },
    addExpenseApprovalsTask: ({workspaceMoreFeaturesLink}) =>
        `*Add expense approvals* to review your team's spend and keep it under control.\n` +
        '\n' +
        `Here's how:\n` +
        '\n' +
        '1. Go to *Workspaces*.\n' +
        '2. Select your workspace.\n' +
        '3. Click *More features*.\n' +
        '4. Enable *Workflows*.\n' +
        '5. Navigate to *Workflows* in the workspace editor.\n' +
        '6. Enable *Add approvals*.\n' +
        `7. You'll be set as the expense approver. You can change this to any admin once you invite your team.\n` +
        '\n' +
        `[Take me to more features](${workspaceMoreFeaturesLink}).`,
}

// GOOD
{
    receiptRequired: ({formattedLimit, category}: ViolationsReceiptRequiredParams) => {
        if (formattedLimit && category) {
            return `Receipt required over ${formattedLimit} category limit`;
        }

        if (formattedLimit) {
            return `Receipt required over ${formattedLimit}`;
        }

        if (category) {
            return `Receipt required over category limit`;
        }

        return 'Receipt required';
    },
    addExpenseApprovalsTask: ({workspaceMoreFeaturesLink}) =>
        dedent(`
            *Add expense approvals* to review your team's spend and keep it under control.

            Here's how:

            1. Go to *Workspaces*.
            2. Select your workspace.
            3. Click *More features*.
            4. Enable *Workflows*.
            5. Navigate to *Workflows* in the workspace editor.
            6. Enable *Add approvals*.
            7. You'll be set as the expense approver. You can change this to any admin once you invite your team.

            [Take me to more features](${workspaceMoreFeaturesLink}).
        `),
    },
}

This provides our AI translation LLM with more context to translate the whole phrase as one string, producing higher quality results.

- Plural forms MUST be handled correctly using plural translation objects

When working with translations that involve plural forms, it's important to handle different cases correctly:

  • zero: Used when there are no items (optional)
  • one: Used when there's exactly one item
  • two: Used when there's two items (optional)
  • few: Used for a small number of items (optional)
  • many: Used for larger quantities (optional)
  • other: A catch-all case for other counts or variations

Example implementation:

messages: () => ({
    zero: 'No messages',
    one: 'One message',
    two: 'Two messages',
    few: (count) => `${count} messages`,
    many: (count) => `You have ${count} messages`,
    other: (count) => `You have ${count} unread messages`,
})

Usage in code:

translate('common.messages', {count: 1});

Translation Generation

- src/languages/en.ts MUST be the source of truth for static strings

src/languages/en.ts is the source of truth for static strings in the App. src/languages/es.ts is (for now) manually-curated. The remainder are AI-generated using scripts/generateTranslations.ts.

- Translation script SHOULD be used for generating non-English translations

The script is run automatically in GH and a diff with the translations is posted as a comment. See example: #70702 (comment)

- Translation quality SHOULD be improved through context and prompt refinement

If you are unhappy with the results of an AI translation, there are two methods of recourse:

  1. Context annotations: If you are adding a string that can have an ambiguous meaning without proper context, you can add a context annotation in en.ts. This takes the form of a comment before your string starting with @context.

  2. Prompt adjustment: The base prompt(s) can be found in prompts/translation, and can be adjusted if necessary.