Text translation

Replace and translate text elements across different languages.

Running this example

To run this example locally:

  1. If you haven't already, create a new app in the Developer Portal(opens in a new tab or window). For more information, refer to our Quickstart guide.

  2. In your app's configuration on the Developer Portal(opens in a new tab or window), ensure the "Development URL" is set to http://localhost:8080.

  3. Clone the starter kit:

    git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git
    cd canva-apps-sdk-starter-kit
    SHELL
  4. Install dependencies:

    npm install
    SHELL
  5. Run the example:

    npm run start text_translation
    SHELL
  6. Click the Preview URL link shown in the terminal to open the example in the Canva editor.

Example app source code

// For usage information, see the README.md file.
import { Button, Rows, Text } from "@canva/app-ui-kit";
import { editContent } from "@canva/design";
import * as styles from "styles/components.css";
import { convertWordsToLorem } from "./lorem_generator";
import { useState } from "react";
const enum Task {
WITH_FORMATTING,
WITHOUT_FORMATTING,
}
export const App = () => {
// This state controls the buttons in the app. It is used to disable them while a translation is in progress.
const [inProgressTask, setInProgressTask] = useState<Task | undefined>(
undefined,
);
/**
* Translates the text on the page while ignoring any inline formatting.
* This implementation provides a simpler approach, making it an ideal starting point
* for understanding the basics of text editing functionality.
*/
const translateWithoutFormatting = async () => {
setInProgressTask(Task.WITHOUT_FORMATTING);
// Read the richtext content in the current page.
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// create a request object by simply reading the plaintext of each text object in the page
const request: string[][] = session.contents.map((range) => [
range.readPlaintext(),
]);
// This simulates an HTTP request to get the translation. For this example we translate into
// lorem ipsum.
const response = await getTranslation(request);
// Take the contents object and get the text ranges. Because of our translate function we know that
// each entry in the response corresponds to a range in the contents array.
session.contents.forEach((range, i) => {
// Get the text length of the existing text in the document
const length = range.readPlaintext().length;
// Replace all of the text in the design with the response from the translation request.
range.replaceText({ index: 0, length }, response[i][0]);
});
// Save the changes to the design
await session.sync();
},
);
setInProgressTask(undefined);
};
/**
* Translates the text in the page while respecting inline formatting.
* If this looks too complicated, look to the `translateWithoutFormatting` method above to help learn the basics.
*/
const translateWithFormatting = async () => {
setInProgressTask(Task.WITH_FORMATTING);
// Read the richtext content in the current page.
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// create a request object by reading the attributed regions of each text object in the page
const request = session.contents.map((range) =>
range.readTextRegions().map((region) => region.text),
);
// This simulates an HTTP request to get the translation. For this example we translate into
// lorem ipsum.
const response = await getTranslation(request);
// For each richtext object we queried, apply the translation
session.contents.forEach((range, index) => {
// Get the translation corresponding to the text object. Each entry contains an array of translations.
// Each translation is an array of strings with each entry corresponding to an entry in the text region array.
const translatedRegions = response[index];
// Get the endpoint of the text represented by the region.
let endOfRegion = range.readPlaintext().length;
// Read the richtext objects as text regions
const regionsToTranslate = range.readTextRegions();
// Reverse the regions and then iterate on them.
// We reverse it because when doing text replacement, changing text length would need us to recalculate regions.
// However ny changing the text from the back first we can avoid these recalculations.
regionsToTranslate.reverse().forEach((region, i) => {
// Get the end of the previous region (which also serves as the start of the current region)
endOfRegion = endOfRegion - region.text.length;
// Replace the current region (starting at the end of the previous region with length equal to the length of the text in the region)
// with the translated text.
range.replaceText(
{
index: endOfRegion,
length: region.text.length,
},
translatedRegions[regionsToTranslate.length - 1 - i],
);
});
});
// Save the changes to the design
await session.sync();
},
);
setInProgressTask(undefined);
};
return (
<div className={styles.scrollContainer}>
<Rows spacing="2u">
<Text>
This example demonstrates how apps can translate all text in the
current page
</Text>
<Button
variant="secondary"
onClick={translateWithFormatting}
disabled={inProgressTask != null}
loading={inProgressTask === Task.WITH_FORMATTING}
>
Translate with Formatting
</Button>
<Button
variant="secondary"
onClick={translateWithoutFormatting}
disabled={inProgressTask != null}
loading={inProgressTask === Task.WITHOUT_FORMATTING}
>
Translate without Formatting
</Button>
</Rows>
</div>
);
};
/**
* A function that simulates a request to translate some text
* @param text the text chunks to translate
* @returns the translated text chunks
*/
async function getTranslation(text: string[][]): Promise<string[][]> {
await new Promise((res) => setTimeout(res, 500));
return text.map((t) => convertWordsToLorem(t));
}
TYPESCRIPT
import { createRoot } from "react-dom/client";
import { App } from "./app";
import "@canva/app-ui-kit/styles.css";
import { AppUiProvider } from "@canva/app-ui-kit";
const root = createRoot(document.getElementById("root") as Element);
function render() {
root.render(
<AppUiProvider>
<App />
</AppUiProvider>,
);
}
render();
if (module.hot) {
module.hot.accept("./app", render);
}
TYPESCRIPT
const lorem =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut aliquam metus ligula, laoreet porttitor quam cursus at. Vivamus non nulla congue, consectetur odio vitae, vehicula sapien. Vestibulum urna augue, commodo sit amet dolor eget, hendrerit porttitor felis. Fusce magna lorem, euismod id nisi eget, malesuada condimentum eros. Etiam lorem odio, bibendum sed vestibulum vel, maximus et urna. Mauris aliquet accumsan neque at tempor. Suspendisse euismod ultricies molestie. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque elementum lacinia magna eu accumsan. Cras vitae massa ut felis maximus accumsan a id nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla volutpat euismod lorem eu bibendum. Integer vitae efficitur quam, eget tristique sapien. Cras tellus risus, efficitur sit amet facilisis dignissim, condimentum vitae nibh. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In neque tellus, ullamcorper id eros ullamcorper, lobortis ornare arcu. Aenean nec neque ut lectus ultricies gravida vitae dictum ligula. Nunc laoreet scelerisque varius. Pellentesque mollis tempor velit, id elementum risus venenatis vel. Curabitur egestas risus tortor, at rutrum mauris convallis sit amet. Aenean metus ante, tincidunt nec consectetur ut, rutrum id urna. Donec convallis consequat auctor. Cras rutrum finibus ligula et feugiat. Morbi dignissim turpis ipsum, eu maximus augue mollis a. Nullam sed lacus tortor. Mauris ut augue vel tortor tempus imperdiet at sed orci. Duis non turpis ut arcu laoreet aliquam. Vivamus arcu ex, feugiat et tempus et, vulputate vel augue. Quisque tempus malesuada cursus. Vivamus quis ultricies erat, mattis dictum neque. Etiam placerat vehicula diam eget tincidunt. Donec at sapien quis mi interdum cursus at vitae arcu. Nullam mattis ipsum ac consectetur tempor. Aliquam erat volutpat. Aliquam scelerisque ipsum id sem scelerisque ullamcorper. Maecenas ut sapien ac purus laoreet venenatis. Suspendisse ac ultrices tellus. Morbi eu leo feugiat, rhoncus ligula ut, aliquet dui. Quisque at nulla est. Vestibulum non quam in mi rutrum sodales gravida vel quam. Nunc felis tellus, fringilla et erat id, cursus consequat libero. Curabitur efficitur, neque vel cursus condimentum, tortor sapien facilisis neque, sed accumsan ex magna a ligula. Donec finibus tempus justo, et semper velit varius eget. Nunc eu porta nisi. In hac habitasse platea dictumst. Etiam tincidunt, sapien non dignissim consectetur, quam ante vestibulum nisi, at hendrerit ipsum orci ut leo. Nulla gravida elit eget ipsum malesuada tincidunt. Donec non tortor feugiat, cursus enim vel, egestas sem. Aliquam sit amet justo vitae ante tempor pellentesque. Curabitur quis lobortis leo. Aenean at semper quam, sed aliquet velit. Maecenas at vulputate tellus, nec hendrerit arcu. Vivamus efficitur est luctus imperdiet placerat.";
const loremWords = lorem.split(" ");
// This function converts words in a string to lorem ipsum
export function convertWordsToLorem(text: string[]): string[] {
let index = 0;
return text.map((t) => {
return t
.split(" ")
.map((w) => {
if (w.length === 0) {
return "";
}
const word = loremWords[index];
index = (index + 1) % loremWords.length;
return word;
})
.join(" ");
});
}
TYPESCRIPT
# Text translation
Demonstrates advanced text transformation patterns including translation with and without formatting preservation. Shows bulk text editing, content session management, and complex text replacement workflows.
For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/text-translation/.
Related examples: See content_replacement/text_replacement for basic text substitution, or content_replacement/richtext_replacement for rich text modifications.
NOTE: This example differs from what is expected for public apps to pass a Canva review:
- Mock translation data is used for demonstration purposes only. Production apps should integrate with real translation services and provide user input interfaces or dynamic content loading
- Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes
- Text format handling is simplified for demonstration. Production apps should handle various text formats and respect formatting preferences
- Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements
MARKDOWN

API Reference

Need Help?