Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
---
title: Localize an app manifest
slug: Web/Progressive_web_apps/How_to/Localize_an_app_manifest
page-type: how-to
sidebar: pwasidebar
---

@hamishwillee hamishwillee Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I localise the rest of the app? We should at least mention that (or if it can't be done, state that too). I assume perhaps we can specify a key for the default language entry point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localizing the rest of the app is a huge topic and out of scope for this article. How you would go about localizing it depends on what kind of app it is, what stack you've used, etc.

https://github.com/oh-jon-paul/awesome-i18n gives you an idea of what's available.

[Progressive Web App (PWA)](/en-US/docs/Web/Progressive_web_apps) manifests can be localized using a combination of localizable members, which have the suffix [`_localized`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/*_localized), and the [`lang`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/lang) and [`dir`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/dir) members.

Manifest localization allows the browser to choose between different text strings (for app names and descriptions, for example) and icons when loading the app to best suit the user's language preferences.

This guide shows how to localize a PWA manifest.

## Example non-localised PWA

The rest of this guide develops a multilingual PWA manifest using the following example as a starting point.

```json
{
"name": "The SuperSausage sausage app",
"short_name": "SuperSausage",
"description": "Find information on all your favorite sausages!",
"start_url": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ab510d",
"icons": [
{
"src": "./icons/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
]
}
```

## Set a default language and direction

The [`lang`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/lang) and [`dir`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/dir) members define a default language and language direction for the app. These will be assumed by the browser if no language and direction more suitable for the user's preferences are found in the [`*_localized`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/*_localized) variants. In such cases, the non-prefixed members are used (for example, `name`).

@hamishwillee hamishwillee Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. What if I don't set a default language or dir, what is used?

  2. While I understand exactly what you mean, this doesn't feel quite right

    These will be assumed by the browser if no language and direction more suitable for the user's preferences are found in the *_localized variants.

    It's because the languages will be used but it isn't lang and dir that will be used. What will be used is the other non-localized members. You clarify this in the next sentence, but "These" still grates.


Set a suitable default language and direction like so:

```json
"lang": "en-US",
"dir": "ltr"
```

## Choose the manifest members you want to localize

The following members support localized variants:

- [`name`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/name)
- [`short_name`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/short_name)
- [`description`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/description)
- [`icons`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/icons)
- [`shortcuts`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/shortcuts)

Our sample manifest has all of these except for `shortcuts`; we'll show how to localize each one.

## Choose the languages you want to support

Generally you should support all of the languages relevant to your major target audience locales.

For this example, we'll choose French (`fr`), German (`de`), Urdu (`ur`), and Japanese (`ja`).

## Localizing the `name` member

Localized versions of the `name` member are contained in the `name_localized` member. Each property inside `name_localized` has to have a key equal to one of the target language's {{glossary("BCP 47 language tag", "BCP 47 language tags")}}, and a value equal to an object or a string representing the localized text for that language.

Let's see what this could look like for our example:

```json
"name_localized": {
"fr": "L'application de saucisse SuperSausage",
"de": "Die SuperWurst-App",
"ur": {
"dir": "rtl",
"value": "سپر ساسیج ساسیج ایپ"
},
"ja": "スーパーソーセージのソーセージアプリ"
}
```

In this case, we've used the string form for all the languages, except for Urdu, which uses the object form. We've done it this way because Urdu is an RTL language, and we need to specify a `dir` value of `rtl` for it so that browsers will display it correctly.

## Localizing the other text-based members

We can follow the same pattern as we did for the `name` member to localize the `short_name` and `description` members:

```json
"short_name_localized": {
"fr": {
"lang": "en-US",
"value": "SuperSausage"
},
"de": "SuperWurst",
"ur": "سپر ساسیج",
"ja": "スーパーソーセージ"
},
"description_localized": {
"fr": "Trouvez des informations sur toutes vos saucisses préférées !",
"de": "Finden Sie Informationen zu all Ihren Lieblingswürstchen!",
"ur": "اپنی تمام پسندیدہ ساسیجز کے بارے میں معلومات حاصل کریں!",
"ja": "お気に入りのソーセージの情報を全部見つけましょう!"
},
```

The French (`fr`) `short_name` translation shows typical usage of the object value form being used to specify a `lang` property. In this case, our French audience knows our app by its English brand name — "SuperSausage" — and we want to specify that this should be handled as English rather than French (for example, for the purposes of pronounciation).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://github.com/mdn/content/pull/44609/changes#r3517481036 - lang in a translation needs additional explanation there. I think I get it now, finally on this the second last doc - this allows you to specify that the value from some other language key should be used for a translation, instead of providing your own, or using the default?

Typo too

Suggested change
The French (`fr`) `short_name` translation shows typical usage of the object value form being used to specify a `lang` property. In this case, our French audience knows our app by its English brand name — "SuperSausage" — and we want to specify that this should be handled as English rather than French (for example, for the purposes of pronounciation).
The French (`fr`) `short_name` translation shows typical usage of the object value form being used to specify a `lang` property. In this case, our French audience knows our app by its English brand name — "SuperSausage" — and we want to specify that this should be handled as English rather than French (for example, for the purposes of pronunciation).


## Localizing the `icon` member

Localized images are handled somewhat differently to localized text. The properties of each `*_localized` member (`icon_localized`, in this case) are equal to an array of objects containing the same properties as the non-localized original (`icon`); each one provides details for a localized image.

Let's see what this looks like:

```json
"icons_localized": {
"fr": [
{
"src": "./icons/l10n/fr/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/fr/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"de": [
{
"src": "./icons/l10n/de/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/de/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"ur": [
{
"src": "./icons/l10n/ur/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/ur/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"ja": [
{
"src": "./icons/l10n/ja/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/ja/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
}
```

## Finished manifest

Putting this all together, the complete manifest looks like this:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need your shortcuts - I want to know how to select my language version.


```json
{
"name": "The SuperSausage sausage app",
"short_name": "SuperSausage",
"description": "Find information on all your favorite sausages!",
"start_url": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ab510d",
"icons": [
{
"src": "./icons/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"lang": "en-US",
"dir": "ltr",
"name_localized": {
"fr": "L'application de saucisse SuperSausage",
"de": "Die SuperWurst-App",
"ur": {
"dir": "rtl",
"value": "سپر ساسیج ساسیج ایپ"
},
"ja": "スーパーソーセージのソーセージアプリ"
},
"short_name_localized": {
"fr": {
"lang": "en-US",
"value": "SuperSausage"
},
"de": "SuperWurst",
"ur": "سپر ساسیج",
"ja": "スーパーソーセージ"
},
"description_localized": {
"fr": "Trouvez des informations sur toutes vos saucisses préférées !",
"de": "Finden Sie Informationen zu all Ihren Lieblingswürstchen!",
"ur": "اپنی تمام پسندیدہ ساسیجز کے بارے میں معلومات حاصل کریں!",
"ja": "お気に入りのソーセージの情報を全部見つけましょう!"
},
"icons_localized": {
"fr": [
{
"src": "./icons/l10n/fr/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/fr/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"de": [
{
"src": "./icons/l10n/de/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/de/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"ur": [
{
"src": "./icons/l10n/ur/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/ur/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"ja": [
{
"src": "./icons/l10n/ja/saus-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "./icons/l10n/ja/saus-256.png",
"sizes": "256x256",
"type": "image/png"
}
]
}
}
```

## See also

- [`dir`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/dir) manifest member
- [`lang`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/lang) manifest member
- [`*_localized`](/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/*_localized) manifest members
- [Localization support for web app manifests](https://developer.chrome.com/blog/manifest-localization) on developer.chrome.com (2026)
Loading