Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Touched upon this in email with Chris, reposting here in more detail.
The dir and lang manifest-level members are somewhat independent of the localization feature.
*_localized members are selected based off of the browser's language setting, not off of the other manifest members.
This is definitely a bit confusing in the spec, as it says the manifest-level dir/lang set the default value of localized members, but the section on localized members clarifies that the default representation is the manifest member without *_localized and localizations should be selected based off of the user's language setting.
The manifest-level dir is defined in the spec as applying a default text direction for localized members that are missing a dir field, but the Chromium implementation only prioritizes whatever direction is specified by the localized member.
The manifest-level lang field isn't actually used when calculating which localized value to show.

In practice (or at least how it works right now in Chromium), this means that the manifest-level lang and dir don't have implications for the localized value that ends up getting chosen. Perhaps we can remove or edit the references to these manifest level members to avoid confusing developers?
The suggested instructions for working with localizable manifests on the chrome developer blog don't mention manifest-level dir or lang as any sort of fallback.

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.

I think I've captured this all in my updates, except maybe this bit:

The manifest-level dir is defined in the spec as applying a default text direction for localized members that are missing a dir field, but the Chromium implementation only prioritizes whatever direction is specified by the localized member.

But I'm not sure if there is really much worth saying.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I agree it's probably not worth specifying. LGTM.


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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  1. At least in the Chromium implementation, if no dir is specified for a localized member, it uses the direction of the browser's currently set language. Localizations are selected based off of the browser's currently set language at all times.

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.

See previous comments.


Set a suitable default language and direction like so:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Consider removing/editing this section as part of comment above.

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.

done


```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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm reading it as clarification that although the localized object is under the French language tag, the value of the object is presented in English rather than French.

From the web application manifest spec:

To support multilingual content and ensure optimal display and accessibility, it is possible to specify a different language for a localized text object. This is needed for situations where a term or text needs to be presented in a language different from the user's set locale.

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.

see above

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.

I've fixed the typo


## 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.

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.

Do you mean you want me to include a shortcuts example?


```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