Migrate an existing app

How to add localization support to an existing app.

This procedure describes how to update an existing app to support Canva’s translation process.

The Canva translation process currently requires use of the react-intl library.

This phase explains how to configure your environment to use the recommended i18n tools.

  1. Install the react-intl, @formatjs/cli, and @canva/app-i18n-kit packages, as well as the latest version of @canva/app-ui-kit:

    npm install @canva/app-i18n-kit react-intl @canva/app-ui-kit@latest && npm install --save-dev @formatjs/cli
    shell

This step explains how to install and configure ESLint. For more information, see the eslint-plugin-formatjs plugin documentation.

  1. Install the eslint-plugin-formatjs plugin. This provides linting rules for the FormatJS library.

    npm install --save-dev eslint eslint-plugin-formatjs
    shell
  2. Add the recommended rules to your eslint.config.mjs:

    import formatjs from "eslint-plugin-formatjs"
    //...
    plugins: {
    formatjs,
    //...
    rules: {
    "formatjs/no-invalid-icu": "error",
    "formatjs/no-literal-string-in-jsx": [
    2,
    {
    props: {
    // These rules are for @canva/app-ui-kit components.
    // For your own components, suppress any false positives using eslint ignore comments.
    include: [
    ["*", "(*Label|label|alt)"],
    ["*", "(title|description|name|text)"],
    ["*", "(placeholder|additionalPlaceholder|defaultValue)"],
    ["FormField", "error"],
    ],
    exclude: [["FormattedMessage", "description"]],
    },
    },
    ],
    "formatjs/enforce-description": ["error", "literal"],
    "formatjs/enforce-default-message": ["error", "literal"],
    "formatjs/enforce-placeholders": "error",
    "formatjs/no-id": "error",
    "formatjs/no-emoji": "error",
    "formatjs/no-useless-message": "error",
    "formatjs/no-multiple-plurals": "error",
    "formatjs/no-offset": "error",
    "formatjs/blocklist-elements": [2, ["selectordinal"]],
    "formatjs/no-complex-selectors": "error",
    js
  1. Configure the FormatJS TS transformer to automatically generate IDs. To do this, add the @formatjs/ts-transformer to your webpack configuration:

    npm install --save-dev @formatjs/ts-transformer
    shell
  2. To generate unique message IDs, add a transformer to your webpack.config.js. This example adds a function called getCustomTransformers:

    const { transform } = require("@formatjs/ts-transformer");
    // ...
    module: {
    rules: [
    {
    test: /\.tsx?$/,
    exclude: /node_modules/,
    use: [
    {
    loader: "ts-loader",
    options: {
    transpileOnly: true,
    getCustomTransformers() {
    return {
    before: [
    transform({
    overrideIdFn: "[sha512:contenthash:base64:6]",
    }),
    ],
    };
    },
    },
    },
    ],
    json
  3. In package.json, add an extract script with the following contents:

    formatjs extract \"src/**/*{ts,tsx}\" --out-file dist/messages_en.json
    json

    Append the extract script to the build script. For example:

    "scripts": {
    "start": "ts-node «/scripts/start/start.ts",
    + "extract": "formatjs extract \"src/**/*{ts,tsx}\" --out-file dist/messages_en.json",
    + "build": "webpack --config webpack.config.js --mode production && npm run extract",
    - "build": "webpack --config webpack.config.js --mode production",
    "lint: types": "tsc",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    diff

Canva's app submission process requires a single app bundle. Implementing localization might increase the size of the app and cause webpack to split the bundle into multiple chunks.

To make sure webpack produces a single bundle, use the following steps:

  1. Open webpack.config.js, and add the optimize module to the webpack import:

    - const { DefinePlugin } = require("webpack");
    + const { DefinePlugin, optimize } = require("webpack");
    diff
  2. In webpack.config.js, add the chunk limit before buildDevConfig is called. Modify this plugins definition:

    plugins: [
    new DefinePlugin({
    BACKEND_HOST: JSON.stringify(backendHost),
    }),
    + new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
    ],
    diff

This phase explains how to update your app to support localization.

Add @canva/app-i18n-kit to your app. You don't need to pass messages or locale into AppI18nProvider, because it internally detects the user's locale and loads the appropriate translated messages. In addition, it also lets you test localization in a later step.

// index.tsx
import { AppUiProvider } from "@canva/app-ui-kit";
import { createRoot } from "react-dom/client";
import { App } from "./app";
import "@canva/app-ui-kit/styles.css";
import { AppI18nProvider } from "@canva/app-i18n-kit";
const root = createRoot(document.getElementById("root") as Element);
function render() {
root.render(
<AppI18nProvider>
<AppUiProvider>
<App />
</AppUiProvider>
</AppI18nProvider>
);
}
ts

The react-intl package lets you use FormattedMessage or the useIntl() hook. We recommend using FormattedMessage wherever possible.

In your app’s code, update the UI strings to use FormattedMessage. For example:

<Text>
<FormattedMessage
description="Label text for a button. Explains that the image will rotate when pressed"
defaultMessage="Rotate the image"
/>
</Text>
ts

If using the useIntl() hook:

const intl = useIntl();
// ...
<Button
variant="primary"
icon={RotateIcon}
ariaLabel={intl.formatMessage({
defaultMessage: "Rotate the image",
description:
"Label text for a button. Explains that the image will rotate when pressed",
})}
/>
ts

This phase explains how to test localization in your app and prepare it for release.

The @canva/app-i18n-kit package lets you test your app using pseudolocalization and lets you change the locale using the Developer menu. This gives you a preview of what your app could look like in a different locale.

The text lets you read your original English text, but inserts non-ASCII characters and adds width to each string to simulate lengthier languages.

  1. Enable pseudolocalization in the Developer menu. This shows you a preview of how different characters and text lengths affect the UI:

    Developer menu with the pseudolocalize option selected

    • Check that any custom components respect the left-to-right and right-to-left setting.
    • Make sure there are no rendering issues or truncated text.

This process extracts all the FormattedMessage strings in your code and saves them to a JSON file. You can then upload this file to Canva for translation.

The recommended tooling automatically generates files in the required format. To generate the messages_en.json file, run the following command:

npm run build
shell

Once this processed has finished, you should see a messages_en.json file appear in the dist directory, alongside your app.js. If this doesn't appear, re-run this step.

As part of the app submission process, you can upload the JSON file for translation:

  1. Locate your app in the Developer Portal.
  2. Upload the messages_en.json file, using the Translations file input.

Canva reviews your app and identifies which locales should be supported. Canva then performs the translation of the supplied strings. You’ll be automatically notified once Canva has finished the translation process. You can then preview your app in the supported locales.