--- title: Localize a POS UI extension description: >- Learn how to localize your POS UI extension for common use cases, including pluralization and number and currency formatting. source_url: html: 'https://shopify.dev/docs/apps/build/pos/tutorials/localize-extension' md: 'https://shopify.dev/docs/apps/build/pos/tutorials/localize-extension.md' --- # Localize a POS UI extension In this tutorial, you'll use JavaScript API functions to localize a POS UI extension that applies a discount to the cart. You'll localize extension text, format discount percentages and monetary values, and handle plural forms for item counts. You can use what you learn here to localize other extensions. ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Create a POS UI extension that appears on the smart grid with some basic localization. * Run the extension locally and test it on a dev store. * Define translation data and localize the following elements: * Numbers using a `formatNumber` function similar to the [`Intl` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) * Currency using a `formatCurrency` `Intl` object * Singular and plural values * Deploy your extension code to Shopify. ## Requirements [Create a Partner account](https://www.shopify.com/partners) [Create a development store](https://shopify.dev/docs/apps/tools/development-stores#create-a-development-store-to-test-your-app) [Scaffold an app](https://shopify.dev/docs/apps/build/scaffold-app) Scaffold an app using Shopify CLI. This tutorial is compatible with the [extension-only template](https://shopify.dev/docs/apps/structure/app-extensions/extension-only-apps). [Manage languages](https://help.shopify.com/en/manual/international/localization-and-translation#managing-languages) You'll need to add and publish a second language to your dev store. You'll also need to [activate the language](https://help.shopify.com/en/manual/international/localization-and-translation#manage-markets-languages) in your dev store's primary market. ## Project [View on GitHub](https://github.com/Shopify/pos-ui-extensions-tutorials/tree/main/example-pos-ui-extensions--localize--preact) ### Create a POS smart grid extension **Note:** If you already have a POS UI extension that you want to localize, then you can skip to [defining translations](#define-translations). #### Create the extension 1. Navigate to your app directory. ## Terminal ```terminal cd ``` 2. Run the following command to create a new extension: ## Terminal ```terminal shopify app generate extension ``` 3. Select **POS smart grid**. You should now have a new extension directory in your app's directory. The extension directory includes the extension script at `src/{FileName}.jsx`. The following is an example directory structure with a `locales` folder added: ## POS UI extension file structure ```text └── my-app └── extensions └── my-pos-ui-extension ├── src │ ├── Modal.jsx // The modal component of the POS UI extension │ └── Tile.jsx // The tile component of the POS UI extension ├── locales │ ├── en.default.json // The default locale for the POS UI extension │ └── fr.json // The locale file for non-regional French translations ├── shopify.extension.toml // The config file for the POS UI extension └── package.json ``` 4. Start your development server to build and preview your app: ## Terminal ```terminal shopify app dev ``` To learn about the processes that are executed when you run `dev`, refer to the [Shopify CLI command reference](https://shopify.dev/docs/api/shopify-cli/app/app-dev). 5. Press `p` to open the developer console. In the developer console page, click on the preview link for your extension. ### Set up the target for your extension Set up the extension target for your POS UI extension. [Extension targets](https://shopify.dev/docs/api/pos-ui-extensions/latest/extension-targets-overview) control where your extension renders in the POS. The example code uses these extension targets: * `pos.home.tile.render` * `pos.home.modal.render` #### Configure the tile target In your extension's [`shopify.extension.toml`](https://shopify.dev/docs/apps/build/app-extensions/configure-app-extensions) configuration file, create `[[extensions.targeting]]` sections with the following information: * `target`: An identifier that specifies where you're injecting code into Shopify. * `module`: The path to the file that contains the extension code. You'll create two targeting sections, one for `pos.home.tile.render` and one for `pos.home.modal.render`. ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] type = "ui_extension" # Change the merchant-facing name of the extension in locales/en.default.json name = "t:name" uid = "11fd348b-1de3-4793-6892-baa416fc3df29b4377b5" handle = "discount" description = "Add discount" # module: file that contains your extension’s source code # target: location where your extension appears in POS [[extensions.targeting]] module = "./src/Tile.jsx" target = "pos.home.tile.render" [[extensions.targeting]] module = "./src/Modal.jsx" target = "pos.home.modal.render" ``` #### Configure the modal target Add another `[[extensions.targeting]]` section for the `pos.home.modal.render` target. This target renders the modal that opens when the tile is tapped. ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] type = "ui_extension" # Change the merchant-facing name of the extension in locales/en.default.json name = "t:name" uid = "11fd348b-1de3-4793-6892-baa416fc3df29b4377b5" handle = "discount" description = "Add discount" # module: file that contains your extension’s source code # target: location where your extension appears in POS [[extensions.targeting]] module = "./src/Tile.jsx" target = "pos.home.tile.render" [[extensions.targeting]] module = "./src/Modal.jsx" target = "pos.home.modal.render" ``` ### Build the POS smart grid extension In this step, you'll build the UI components for your POS extension. You'll create a Tile that appears on the smart grid and a Modal that opens when the tile is tapped. #### Build the Tile UI The Tile component renders on the POS smart grid and opens a Modal when tapped. Add the following code to create a tile that displays discount information and triggers the discount modal. ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Tile.jsx ```jsx import {render} from 'preact'; import {useState, useEffect} from 'preact/hooks'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); } function Extension() { const {i18n} = shopify; const shouldDisable = (subtotal) => { return Number(subtotal) <= 100; }; const [disabled, setDisabled] = useState(shouldDisable(shopify.cart.current.value.subtotal)); useEffect(() => { const unsubscribe = shopify.cart.current.subscribe((cart) => { setDisabled(shouldDisable(cart.subtotal)); }); return unsubscribe; }, []); return ( shopify.action.presentModal()} disabled={disabled} /> ); } ``` #### Build the modal UI Using web components, add buttons and text to the modal. This example also uses a tile component to trigger the modal. *** [s-button](https://shopify.dev/docs/api/pos-ui-extensions/latest/polaris-web-components/actions/button) [s-text](https://shopify.dev/docs/api/pos-ui-extensions/latest/polaris-web-components/structure/text) [s-scroll-box](https://shopify.dev/docs/api/pos-ui-extensions/latest/polaris-web-components/structure/scrollbox) ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` ### Define translations To define translations, you'll adjust the `[locale].json` files in the `extensions//locales` folder within your app. **Tip:** In this tutorial, you'll keep French (`fr`, non-regional) as an available locale. However, you can also create translations for additional locales. #### Set the default locale Your default locale specifies which locale Shopify should use when no other appropriate locale can be matched. In this example, English (`en`) is already the default locale. However, you can set any locale to be your default locale. To change your default locale, go to the `locales` folder and change the `[locale].json` filename to `[locale].default.json`. #### Add translation strings for `en.default.json` Add the translation strings you need to support English translations, including the `zero`, `one`, and `other` plural rules. You can specify any pluralization key that [`Intl.PluralRules.select()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/select) supports and that's appropriate for the locale. In subsequent steps, you'll use these keys to translate [`itemCount`](#translate-the-items-in-cart-message). #### Add translation strings for `fr.json` Now add the translation strings you need to support French translations, `zero`, `one` and `other` plural rules. Similar to English, you can specify any pluralization key that [`Intl.PluralRules.select()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/select) supports and that's appropriate for the locale. ### Localize the currency Now that you've defined translations, you'll localize the currency: Add the `formatCurrency` function provided by `i18n`. The function wraps the standard [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) object. Depending on the current locale, `10` resolves to the following localized currency formats: * **`en`**: `$10.00` * **`fr`**: `10,00 $` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` ### Localize numbers In this step, you'll resolve localized numbers: Localize number formatting using the `formatNumber` function provided by `i18n`. The function wraps the standard [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) object. Depending on the current locale, `25.5` resolves to one of the following localized number formats: * **`en`**: `25.5` * **`fr`**: `25,5` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` ### Translate the discount applied message In this step, you'll translate the discount applied message: Call the `translate` function, which sends the `discountApplied` variable so that it can be used in the translation string. ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` ### Translate the items in cart message In this step, you'll translate the `itemCount` message, which supports pluralization: Use the `translate` function to pass in the `count` of how many items are in the cart. The `i18n` system uses `count` to render the appropriate plural form in the current locale. ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` **Note:** When working with translation keys with pluralization, you must provide the `count` property. This enables the `translate` function to determine which pluralization to use, according to [Intl Pluralization Rules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/select). ### Preview the extension Preview your extension to make sure it works as expected. ### Start your server 1. Navigate to your app directory and start your development server. 2. Press `p` to open the developer console. 3. In the developer console, click on **view mobile** or scan the provided QR code to preview your extension. #### Test the extension functionality The POS UI extension should now render localized content for `en` and `fr`. To test the extension, change the language in your device settings. ### Deploy and release Congratulations! You've created a POS UI extension with region-specific localization. Now you can [deploy and release your extension](https://shopify.dev/docs/apps/deployment/app-versions). ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] type = "ui_extension" # Change the merchant-facing name of the extension in locales/en.default.json name = "t:name" uid = "11fd348b-1de3-4793-6892-baa416fc3df29b4377b5" handle = "discount" description = "Add discount" # module: file that contains your extension’s source code # target: location where your extension appears in POS [[extensions.targeting]] module = "./src/Tile.jsx" target = "pos.home.tile.render" [[extensions.targeting]] module = "./src/Modal.jsx" target = "pos.home.modal.render" ``` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Tile.jsx ```jsx import {render} from 'preact'; import {useState, useEffect} from 'preact/hooks'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); } function Extension() { const {i18n} = shopify; const shouldDisable = (subtotal) => { return Number(subtotal) <= 100; }; const [disabled, setDisabled] = useState(shouldDisable(shopify.cart.current.value.subtotal)); useEffect(() => { const unsubscribe = shopify.cart.current.subscribe((cart) => { setDisabled(shouldDisable(cart.subtotal)); }); return unsubscribe; }, []); return ( shopify.action.presentModal()} disabled={disabled} /> ); } ``` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/src/Modal.jsx ```jsx import {render} from 'preact'; // Allows the use of `shopify.cart.current.value` as a stateful subscription. import '@shopify/ui-extensions/preact'; export default async () => { render(, document.body); }; function Extension() { const {i18n} = shopify; const percentageDiscount = 25.5; const formattedPercentageDiscount = i18n.formatNumber(percentageDiscount); const fixedDiscountAmount = 10; const formattedFixedDiscountAmount = i18n.formatCurrency(fixedDiscountAmount, {currency: 'CAD'}); const itemCount = shopify.cart.current.value.lineItems.length; const onButtonClick = (type, title, amount) => { shopify.cart.applyCartDiscount(type, title, amount); shopify.toast.show(i18n.translate('discountApplied')); }; return ( {i18n.translate('itemCount', {count: itemCount})} onButtonClick('Percentage', '25% off', '25')}> {formattedPercentageDiscount}% onButtonClick('FixedAmount', '$10 off', '10')}> {formattedFixedDiscountAmount} ); } ``` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/locales/en.default.json ```json { "name": "Discount", "itemCount": { "zero": "No items in cart", "one": "{{count}} item in cart", "other": "{{count}} items in cart" }, "availableDiscounts": "View discount options", "discountApplied": "Discount applied!", "modalTitle": "Choose a discount" } ``` ## /example-pos-ui-extensions--localize--preact/extensions/my-pos-ui-extension/locales/fr.json ```json { "name": "Réduction", "itemCount": { "zero": "Aucun article dans le panier", "one": "{{count}} article dans le panier", "other": "{{count}} articles dans le panier" }, "availableDiscounts": "Voir les options de réduction", "discountApplied": "Réduction appliquée!", "modalTitle": "Choisir une réduction" } ```