-
Notifications
You must be signed in to change notification settings - Fork 23.2k
Technical review: Document web app manifest localization #44609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
026f7d4
b7dca24
3d14e52
30c3868
32f40cd
ff1e357
ca38a9f
ae6c7b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||||||
| --- | ||||||
|
|
||||||
| [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`). | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
|
||||||
| 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). | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See https://github.com/mdn/content/pull/44609/changes#r3517481036 - Typo too
Suggested change
|
||||||
|
|
||||||
| ## 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: | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.