Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
Text translation
Replace and translate text elements across different languages.
Running this example
To run this example locally:
-
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.
-
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
. -
Clone the starter kit:
git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.gitcd canva-apps-sdk-starter-kitSHELL -
Install dependencies:
npm installSHELL -
Run the example:
npm run start text_translationSHELL -
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 pageawait 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 designsession.contents.forEach((range, i) => {// Get the length of the original text to know how much to replaceconst length = range.readPlaintext().length;// Replace the entire text content with the translated textrange.replaceText({ index: 0, length }, response[i][0]);});// Commit all changes to the design - this makes the changes visible to the userawait 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 pageawait 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 formattingsession.contents.forEach((range, index) => {// Get the translated regions corresponding to this text elementconst translatedRegions = response[index];// Track position from the end of the text to avoid index recalculation during replacementlet endOfRegion = range.readPlaintext().length;// Get all text regions with their formatting informationconst regionsToTranslate = range.readTextRegions();// Process regions in reverse order to avoid position shifts affecting subsequent replacementsregionsToTranslate.reverse().forEach((region, i) => {// Calculate the start position of the current regionendOfRegion = 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 userawait session.sync();},);setInProgressTask(undefined);};return (<div className={styles.scrollContainer}><Rows spacing="2u"><Text>This example demonstrates how apps can translate all text in thecurrent page</Text><Buttonvariant="secondary"onClick={translateWithFormatting}disabled={inProgressTask != null}loading={inProgressTask === Task.WITH_FORMATTING}>Translate with formatting</Button><Buttonvariant="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 APIawait new Promise((res) => setTimeout(res, 500));// Convert to lorem ipsum as a placeholder for actual translationreturn 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 exampleconst 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 structureexport 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 translationDemonstrates 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?
- Join our Community Forum(opens in a new tab or window)
- Report issues with this example on GitHub(opens in a new tab or window)