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);
// Start a content editing session for all richtext elements on the current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Extract plaintext from each richtext element, ignoring any formatting like bold, italic, etc.
const request: string[][] = session.contents.map((range) => [
range.readPlaintext(),
]);
// Simulate a translation API call (in production, this would call a real translation service)
const response = await getTranslation(request);
// Apply translations to each richtext element in the design
session.contents.forEach((range, i) => {
// Get the length of the original text to know how much to replace
const length = range.readPlaintext().length;
// Replace the entire text content with the translated text
range.replaceText({ index: 0, length }, response[i][0]);
});
// Commit all changes to the design - this makes the changes visible to the user
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);
// Start a content editing session for all richtext elements on the current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Extract text regions which preserve formatting boundaries (bold, italic, etc.)
const request = session.contents.map((range) =>
range.readTextRegions().map((region) => region.text),
);
// Simulate a translation API call (in production, this would call a real translation service)
const response = await getTranslation(request);
// Apply translations to each richtext element while preserving formatting
session.contents.forEach((range, index) => {
// Get the translated regions corresponding to this text element
const translatedRegions = response[index];
// Track position from the end of the text to avoid index recalculation during replacement
let endOfRegion = range.readPlaintext().length;
// Get all text regions with their formatting information
const regionsToTranslate = range.readTextRegions();
// Process regions in reverse order to avoid position shifts affecting subsequent replacements
regionsToTranslate.reverse().forEach((region, i) => {
// Calculate the start position 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],
);
});
});
// Commit all changes to the design - this makes the changes visible to the user
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>
);
};
/**
* Mock function that simulates calling an external translation API.
* In a production app, this would make HTTP requests to services like Google Translate,
* AWS Translate, or Azure Translator Text.
* @param text Array of text chunks to translate, grouped by richtext element
* @returns Promise resolving to translated text chunks in the same structure
*/
async function getTranslation(text: string[][]): Promise<string[][]> {
// Simulate network delay that would occur with a real translation API
await new Promise((res) => setTimeout(res, 500));
// Convert to lorem ipsum as a placeholder for actual translation
return text.map((t) => convertWordsToLorem(t));
}
TYPESCRIPT
// For usage information, see the README.md file.
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();
// Hot Module Replacement for development (automatically reloads the app when changes are made)
if (module.hot) {
module.hot.accept("./app", render);
}
TYPESCRIPT
// For usage information, see the README.md file.
// Static lorem ipsum text used to simulate translation results in this example
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(" ");
// Converts text arrays to lorem ipsum words while preserving word count and structure
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?