ICU syntax

Canva supports a subset of International Components for Unicode (ICU).

The Canva translation process supports a subset of ICU MessageFormat syntax. For more information about ICU, see the official FormatJS documentation.

This section describes the supported ICU syntax, with recommendations and examples.

Follow these examples and guidelines so that your app can be translated more accurately and efficiently.

How to do basic message localization with React, using the FormattedMessage component from react-intl.

  • The defaultMessage string is the source for translation text. It's displayed to users that either have an English locale, or a locale with no translations available.
  • Use the description string to give as much context as possible to a human translator. For guidance on writing translator notes, see Add notes for translators.

For example:

<FormattedMessage
defaultMessage="My internationalized app"
description="The title the user sees when opening the app. Appears at top of page."
/>
tsx
Do
Ensure that defaultMessage is clear and written in US English.
Include a detailed description that gives context to translators.

You can use dynamic values (like firstName) in the defaultMessage prop of FormattedMessage. This process is known as interpolation.

This section explains how to use interpolation for localized messages in React, using the FormattedMessage component from react-intl.

For example:

const name = "John"
// ...
<FormattedMessage
defaultMessage="Welcome to the world of AI creativity, {firstName}!"
description="A greeting that welcomes the user to the AI image generation app"
values={{
firstName: name,
}}
/>
tsx

This renders as: "Welcome to the world of AI creativity, John!"

Do
When inserting dynamic content (such as user-specific data), use placeholders like {firstName}.
"Hello {name}"
Don't
Don't manually combine strings and variables, using techniques like the + operator. This can lead to incorrect and confusing sentences when translations are combined. This is because languages will reorder words in various ways. For more information, see this blog post.
"Hello " + name

How to handle plurals in localized messages in React.

For example:

export const CreditUsage = ({
creditsCost,
remainingCredits,
}: {
creditsCost: number;
remainingCredits: number;
}) => (
<Text>
<FormattedMessage
defaultMessage={`Use {creditsCost, number} of {remainingCredits, plural,
one {# credit}
other {# credits}
}`}
description="Informs the user about the number of credits they will use for the image generation task. Appears below the image generation button."
values={{
creditsCost,
remainingCredits,
}}
/>
</Text>
);
tsx
<CreditUsage creditsCost={5} remainingCredits={50} />
<CreditUsage creditsCost={1} remainingCredits={1} />
tsx

This would render as:

  • "Use 5 of 50 credits"
  • "Use 1 of 1 credit"
Do
Consider edge cases: Ensure that pluralization handles various scenarios, such as zero, one, and other amounts.

When passing number, date, or time values, make sure you use the correct ICU argument so that the values are formatted in a way that respects the user's locale.

This examples shows how to format numbers in localized messages in React, using the FormattedMessage component from react-intl.

<FormattedMessage
defaultMessage="Image generation is {progress, number, ::percent} complete."
description="Displays the progress of the current image generation task that the user requested"
values={{
progress: 0.75,
}}
/>
tsx

Assuming the locale uses this percentage format, this can render as: "Image generation is 75% complete."

Do
Use ICU number formatting for numbers, such as percentages.

Members ({(members, number, integer)}) - 123456 members displays as Members (123,456) in English, Membres (123 456) in French, and Thành viên (123.456) in Vietnamese.

Don't
Avoid formatting numbers manually. Instead, you can use an ICU formatting library like react-intl.

Members ({members}) - The number doesn't respect the user's locale and always displays 123456 as the number, without separators.

This example demonstrates how to format dates and times in localized messages in React, using the FormattedMessage component from react-intl.

<FormattedMessage
defaultMessage="Credits refresh on: {refreshDate, date, short} at {refreshTime, time, short}"
description="Informs users when their credits for image generation will refresh, including the time"
values={{
refreshDate: new Date("2023-11-24T15:00:00"),
refreshTime: new Date("2023-11-24T15:00:00"),
}}
/>
tsx

Depending on the user's locale, this can render as "Credits refresh on: 11/24/23 at 3:00 PM"

Do

Expires {(expiryDate, date, short)} - When formatted with a date value (such as new Date('2024-09-25') in JavaScript), this will correctly display as Expires 9/25/2024 for US English (en-US) and Verfällt am 25.09.2024 for German (de-DE).

Don't

Expires {expiryDate} - The number doesn't respect the user’s locale and always displays the date as 2024-09-25. For example: (Verfällt am 2024-09-25)

Do

Current time: {(currentTime, time, short)} - This displays as Current time: 3:45 PM in English.

This section provides guidelines for displaying relative time.

const LastGeneratedMessage = ({
lastGeneratedTime,
}: {
lastGeneratedTime: Date;
}) => {
const intl = useIntl();
const [generatedTimeAgoInSeconds, setGeneratedTimeAgoInSeconds] = React.useState(0);
// ...
return (
<Text>
<FormattedMessage
defaultMessage="Last image generated {timeAgo}"
description="Tells the user how long ago they generated their last image. timeAgo is a relative time string. e.g. '5 seconds ago'"
values={{
timeAgo: intl.formatRelativeTime(
-generatedTimeAgoInSeconds,
"seconds",
),
}}
/>
</Text>
);
};
tsx
Do
Use functions or libraries that handle relative time calculations and formatting, such as intl.formatRelativeTime of react-intl, or Intl.RelativeTimeFormat
Don't
Avoid hardcoding custom logic for relative time calculations, as this might not adapt well across different locales.
Do

Hello, {role, select, admin {Administrator} user {User} guest {Guest} other {Visitor}}!

  • If role is 'admin', it renders: Hello, Administrator!
  • If role is 'user', it renders: Hello, User!
  • If role is 'guest', it renders: Hello, Guest!
  • If role is anything else or missing, it renders: Hello, Visitor!

How to format rich text in localized messages in React, using the FormattedMessage component from react-intl.

For example:

<FormattedMessage
defaultMessage="Discover stunning AI-generated example images in our <link>gallery</link> and <callToAction>start exploring now!</callToAction>"
description="A call to action directing the user to explore the AI image gallery"
values={{
link: (chunks) => (
<Link
href={DOCS_URL}
requestOpenExternalUrl={() => openExternalUrl(DOCS_URL)}
>
{chunks}
</Link>
),
callToAction: (chunks) => <strong>{chunks}</strong>,
}}
>
{(chunks) => <Text>{chunks}</Text>}
</FormattedMessage>
tsx

This can render as: "Discover stunning AI-generated example images in our gallery and start exploring now!"

Do
Use functions for rich text: To ensure dynamic content is properly rendered, you can wrap parts of your message in React components by passing functions in the values prop.
Don't
Hardcode HTML Elements: Avoid embedding raw HTML within your defaultMessage.

How to use localized strings as button labels in React, using formatMessage from react-intl.

For example:

<Button variant="primary">
{intl.formatMessage({
defaultMessage: "Generate image",
description: "A button label to generate an image from a prompt",
})}
</Button>
tsx

This renders a button with the label "Generate image", and the text is localized based on the user's locale.

Do
For consistency with the rest of the app's localization, use formatMessage to localize user-facing text that is required to be a string type (like button labels).
Don't
Avoid hardcoding button text, since it will be left untranslated.

How to use non-visible text for accessibility purposes (such as screen reader descriptions) in React, using ariaLabel and formatMessage from react-intl.

For example:

<Button
variant="primary"
icon={SortIcon}
ariaLabel={intl.formatMessage({
defaultMessage: "Sort images by creation date (Newest to Oldest)",
description:
"Screenreader text for a button. When pressed, the button will sort the generated images by creation date from newest to oldest.",
})}
/>
tsx

The screen reader will read the aria label as: "Sort images by creation date (Newest to Oldest)".

Do
Use formatMessage for accessibility by providing localized ariaLabel text.

To support left-to-right (LTR) and right-to-left (RTL) languages, you can use CSS properties in UI components.

This example uses paddingStart instead of paddingLeft or paddingRight, which lets your UI automatically adapt to the language's text direction.

<Box paddingStart="2u">
<Slider min={0} max={100} />
</Box>
tsx
Do
To help components adapt to LTR and RTL languages, use logical properties like paddingStart instead of directional properties like paddingLeft.

How to format lists in a way that is compatible with localization.

In this example, the SelectedEffects component defines a list of image effects, each formatted for localization.

const SelectedEffects = () => {
const intl = useIntl();
// TODO: Make this list change based on user selection!
const selectedEffects = [
intl.formatMessage({
defaultMessage: "black and white",
description:
"An option that when selected, will apply a black and white effect to the generated image",
}),
intl.formatMessage({
defaultMessage: "high contrast",
description:
"An option that when selected, will apply a high contrast effect to the generated image",
}),
intl.formatMessage({
defaultMessage: "cartoon",
description:
"An option that when selected, will apply a cartoon effect to the generated image",
}),
];
return (
<Text>
<FormattedMessage
defaultMessage="You have selected the following image effects: {effects}"
description="Informs the user about the image effects they have selected. effects is a list of effects that will be applied to the generated image."
values={{
effects: intl.formatList(selectedEffects, {
type: "conjunction",
}),
}}
/>
</Text>
);
};
tsx

This component renders based on the user's locale. For example, an English list uses an Oxford comma, but German does not:

  • English (en-US): "You have selected the following image effects: black and white, high contrast, and cartoon."
  • German (de-DE): "Sie haben die folgenden Bildeffekte ausgewählt: Schwarzweiß, hoher Kontrast und Cartoon."
Do
Ensure that lists are formatted using methods that adapt to locale-specific conventions, such as comma placement and conjunction usage. Use library functions like intl.formatList of react-intl to automatically handle these variations, or use built-in JS APIs like Intl.ListFormat
Don't
Avoid concatenating list items by using separators like commas or "and," as this might not be appropriate for all languages and locales.
Don't overlook variations in list formatting conventions between different languages.

How to use display name formatting to inform users about the app's active language, using the FormattedMessage component with React.

For example:

<FormattedMessage
defaultMessage="You are currently viewing this app in {language}"
description="Shows the user which language the app is currently using"
values={{
language: intl.formatDisplayName(intl.locale, {
type: "language",
}),
}}
/>
tsx
Do
Use intl.formatDisplayName or Intl.DisplayNames to retrieve and display the name of the active language.
Don't
Avoid hardcoding language names directly in the UI, as this limits flexibility and might not adapt to all locale-specific language names.
Don't use strings with multiple plural arguments. For example:

{like_count, plural, one {1 like} other {{like_count} likes}} and {subscriber_count, plural, one {1 sub} other {{subscriber_count} subs}}

Don't use strings with a plural argument and offset property. For example:

{like_count, plural, offset:1 =0 {no likes} other {{like_count} likes}}

Don't use strings with a selectordinal argument. For example:

{n, selectordinal, one {This is the #st item} two {This is the #nd item} few {This is the #rd item} other {This is the #th item}}

Don't use strings containing the choice argument. For example:

{user_category, choice, ...

Don't use strings that result in over 20 combinations. For example:
{firstChoice, select,
optionA {First A}
optionB {First B}
other {First C}
} {secondChoice, select,
optionX {Second X}
optionY {Second Y}
other {Second Z}
} {thirdChoice, select,
option1 {Third 1}
option2 {Third 2}
other {Third 3}
}
ts
  • This example contains a select with 3 choices, followed by another select with 3 choices, followed by another select with 3 choices. This would result in 3 x 3 x 3 = 27 possible string combinations.
Don't use nested selects/plurals/select-plural combinations, except when the nested argument is a simple placeholder. For example, this nesting is not supported:
{gender, select,
male {
{count, plural,
one {He has one item}
other {He has # items}
}
}
other {
{count, plural,
one {They have one item}
other {They have # items}
}
}
}
ts
This nesting is supported:
{task_count, plural,
one {
You have {# task_count} task: {task_name}.
}
other {
You have {# task_count} tasks. Next task: {task_name}.
}
}
ts