Skip to content

Single PWA with i18n #43796

Closed as not planned
Closed as not planned

Description

@TheSlimvReal

Which @angular/* package(s) are relevant/releated to the feature request?

localize, service-worker

Description

Following up on this post on stackoverflow.

After adding internationalization (i18n) to our app we run into the big problem of backwards compatibility. Before adding i18n we had our service worker at the root folder and after adding i18n we now have a separate service worker for each language we translated the app into. This is a problem because a user that installed the app prior to this change will not correctly get updates because the old service worker will not be able to find the new service worker.

This is the default behavior of Angular and left us with the decision that we either have to tell all our users that they will have to uninstall and re-install the app or add a service worker in the root folder that automatically takes care of the migration and leave it deployed until all users are migrated. Theses solutions are rather unsatisfying, poorly documented and mean that a we would have to go through the same struggle again if we decide to remove the i18n or take a different approach for it.

Proposed solution

It would be great if Angular would not enforce to use the one-PWA-per-language approach but rather leave this as a choice to the developer. This would make the transition from a single- to a multi-language app much easier and leaves the option open to also go back to the previous approach (no i18n).

This could be configured through the compiler options in the angular.json file. E.g. rootServiceWorker: true.

Alternatives considered

Alternatively, it would be great if Angular provided a guide or a predefined service worker (similar to the safety-worker.js) that allows a easy migration of the PWAs when i18n is introduced.

Activity

  1. added
    area: i18nIssues related to localization and internationalization
    on Oct 11, 2021
  2. added this to the needsTriage milestone on Oct 11, 2021
  3. TheSlimvReal commented on Oct 21, 2021

    @TheSlimvReal
    Author

    We are currently using the following script to merge the service workers and unregister the old ones:

    const fs = require("fs");
    
    const distFolder = "dist";
    
    function getNgswConfig(locale) {
      const swConfig = fs.readFileSync(`${distFolder}/${locale}/ngsw.json`);
      return JSON.parse(swConfig);
    }
    
    const locales = fs
      .readdirSync(distFolder)
      .filter((locale) => fs.lstatSync(`${distFolder}/${locale}`).isDirectory());
    
    // Merge the ngsw.json files
    const firstLocale = locales.pop();
    const combined = getNgswConfig(firstLocale);
    locales.forEach((locale) => {
      const additional = getNgswConfig(locale);
    
      // Merge data and asset groups
      const toBeMergedGroups = [
        { groupType: "dataGroups", property: "patterns" },
        { groupType: "assetGroups", property: "urls" },
      ];
      toBeMergedGroups.forEach(({ groupType, property }) => {
        additional[groupType].forEach((group) => {
          combined[groupType]
            .find((g) => g.name === group.name)
            [property].push(...group[property]);
        });
      });
    
      // combine hash tables
      Object.assign(combined.hashTable, additional.hashTable);
      fs.unlinkSync(`${distFolder}/${locale}/ngsw.json`);
      fs.unlinkSync(`${distFolder}/${locale}/ngsw-worker.js`);
      fs.renameSync(
        `${distFolder}/${locale}/safety-worker.js`,
        `${distFolder}/${locale}/ngsw-worker.js`
      );
    });
    
    combined.index = "/index.html";
    // add root index.html to correctly cache direct app entry (e.g. without en-US)
    combined["assetGroups"][0]["urls"].push("/index.html");
    
    fs.writeFileSync(`${distFolder}/ngsw.json`, JSON.stringify(combined));
    fs.unlinkSync(`${distFolder}/${firstLocale}/ngsw.json`);
    
    // Adjust service worker to allow changing language offline
    const swFile = fs
      .readFileSync(`${distFolder}/${firstLocale}/ngsw-worker.js`)
      .toString();
    const patchedSw = swFile.replace(
      "return this.handleFetch(this.adapter.newRequest(this.indexUrl), context);",
      `const locale = this.adapter.normalizeUrl(req.url).split("/")[1];
                        const url = locale ? "/" + locale + "/index.html": "/index.html";
                        return this.handleFetch(this.adapter.newRequest(url), context);`
    );
    fs.writeFileSync(`${distFolder}/ngsw-worker.js`, patchedSw);
    fs.unlinkSync(`${distFolder}/${firstLocale}/ngsw-worker.js`);
    fs.renameSync(
      `${distFolder}/${firstLocale}/safety-worker.js`,
      `${distFolder}/${firstLocale}/ngsw-worker.js`
    );

    This script:

    1. merges all the ngsw.json files into a single one that is placed at root
    2. patches the ngsw-worker.js file to allow changing languages offline and places it at root
    3. removes all the ngsw.json files in the locale folders
    4. Replaces the ngsw-worker.js files with the safety-worker.js files in the locale folders to unregister all old service workers that might still be present on the users browsers

    Additionally we register the service worker like this:

        ServiceWorkerModule.register("/ngsw-worker.js", {
          enabled: environment.production,
        }),

    And our manifest file has this config:

    {
      "scope": "/",
      "start_url": "/",
      ...
    }

    More information can be found in this repo where we use this approach in production.

  4. added
    featureLabel used to distinguish feature request from other issues
    on Oct 21, 2021
  5. modified the milestones: needsTriage, Backlog on Oct 21, 2021
  6. petebacondarwin commented on Oct 21, 2021

    @petebacondarwin
    Contributor

    Hi @TheSlimvReal - sorry not to respond so far on this issue. It is a bit tricky to implement on our side. I need to talk with @gkalpak about how this could work - I am not sure if it is something we would do. I am glad that for the time being you appear to have a workaround.

  7. angular-robot commented on Oct 21, 2021

    @angular-robot
    Contributor

    This feature request is now candidate for our backlog! In the next phase, the community has 60 days to upvote. If the request receives more than 20 upvotes, we'll move it to our consideration list.

    You can find more details about the feature request process in our documentation.

  8. petebacondarwin commented on Oct 21, 2021

    @petebacondarwin
    Contributor

    Can you clarify what the problem is for new users?

    Is it that when they arrive at https://my.domain.com/ they hit the old SW, but really you want them to be redirected to some locale specific URL like https://my.domain.com/en-US?

  9. TheSlimvReal commented on Oct 21, 2021

    @TheSlimvReal
    Author

    No, the problem initially came from us adding i18n to our app but old users, who installed the app before i18n was added, never got updates after that because the location of the service worker file changed.

    One solution is to tell all users to reinstall the app which is a rather difficult process in our case and it might introduce the same problem again if we decide to remove i18n or take a different approach. Therefore, it would be great if there would be an option to keep the service worker at the root folder (like what the script does that I have provided). Also this could allow users to change the language offline without having to install all languages as separate PWAs but that's probably a different question.

  10. angular-robot commented on Nov 30, 2021

    @angular-robot
    Contributor

    Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

    Find more details about Angular's feature request process in our documentation.

  11. added
    feature: under considerationFeature request for which voting has completed and the request is now under consideration
    and removed on Dec 1, 2021
  12. ValentinFunk commented on Dec 29, 2021

    @ValentinFunk

    If you load your translation files at runtime using @angular/localize you can have a single pwa with multiple languages. You can still lazy load the translation files so that all languages are not part of your main bundle

  13. TheSlimvReal commented on Jan 18, 2022

    @TheSlimvReal
    Author

    If you load your translation files at runtime using @angular/localize you can have a single pwa with multiple languages. You can still lazy load the translation files so that all languages are not part of your main bundle

    @kamshak What exactly are you revering to when you talk about at runtime? @angular/localize uses the translation files at build time and creates a single application for each language. The translation files are not part of the final main bundle.

  14. ValentinFunk commented on Jan 18, 2022

    @ValentinFunk

    If you load your translation files at runtime using @angular/localize you can have a single pwa with multiple languages. You can still lazy load the translation files so that all languages are not part of your main bundle

    @kamshak What exactly are you revering to when you talk about at runtime? @angular/localize uses the translation files at build time and creates a single application for each language. The translation files are not part of the final main bundle.

    Yes, you can build separate application bundles for each language (inlining the translations). You use @angular/localize only for tagging strings. As a postbuild step the CLI will basically get rid of all the $localize calls in your code by replacing the calls to it with the translations more or less directly. The issue is you need to now serve separate PWAs for each language because you essentially have different apps with different file hashes and everything.

    Another option is to ship translation files with your app and load them at run time (i.e. generate one app bundle + multiple files containing only the messages). That way Angular will keep the actual calls to $localize in your app, which then perform the translations when the app is running. To do this you include @angular/localize/init and call loadTranslations at runtime. Now what I'm referring to is this: you need to have messages available so you want to ship the translation file with your app. But you don't want to include ALL languages into your main bundle. So you can write a small bit of bootstrap logic to get the user's language, then load only the needed translation file on demand before the app bootstraps. I can do a small writeup on how to wire this up exactly if there's interest

  15. TheSlimvReal commented on Jan 18, 2022

    @TheSlimvReal
    Author

    @kamshak Thank you for pointing that out, I had no idea that this loadTranslations function exists. I guess I have to try out whether the performance is still alright. At least it keeps the PWA size smaller. Have you ever worked with that approach?

  16. ValentinFunk commented on Jan 18, 2022

    @ValentinFunk

    @kamshak Thank you for pointing that out, I had no idea that this loadTranslations function exists. I guess I have to try out whether the performance is still alright. At least it keeps the PWA size smaller. Have you ever worked with that approach?

    Have a PWA in production with this, it's working great so far

  17. TheSlimvReal commented on Apr 22, 2022

    @TheSlimvReal
    Author

    @kamshak Sorry for the long silence. My previously mentioned workaround was working quite well but now we are running into some new problems regarding the service-workers and I would like to try out your approach.

    Just out of curiosity: How do you create your translation files for the loadTranslations function? Do you use the XLIF format and then parse it using something like the merge script described here?
    It would be great if you could point me to a reference implementation where loadTranslations is used as the docs are very sparse on that point (especially regarding caching and bootstrap).

  18. perkoe commented on Aug 4, 2025

    @perkoe

    Hi @TheSlimvReal and @ValentinFunk,

    I've encountered similar issues with localization and service worker integration, particularly around Angular's built-in internationalization and PWA caching strategies.

    You mentioned previously running into additional problems with your original workaround related to service workers, and then moving toward Angular's loadTranslations method instead. Could you elaborate briefly on:

    1. What specific new issues did you face regarding service workers with the initial approach (the merge-script method)?

    2. Did moving to the loadTranslations method completely solve those issues for you, especially regarding caching, bootstrap performance, and size?

    3. Could you share a reference implementation or briefly outline how you structured your translation files, managed caching, and handled bootstrap performance with loadTranslations?

    I'd greatly appreciate any insights or examples you can provide, as official documentation is sparse and your experience would be incredibly valuable.

    Additionally, I noticed your original merge script doesn't copy the index.htm from the primary locale into the root directory (dist/webapp/browser). Was there a specific reason behind leaving index.html localized rather than placing a default one at the root?

    I'm curious if this was intentional for handling locales separately or if it was an oversight. How did you manage the root-level index.html serving when using this approach?

  19. JeanMeche commented on Aug 29, 2025

    @JeanMeche
    Member

    With #63364 we've announced that the service-worker package in now in maintenance mode and will not receive feature PRs and feature requests.

    The message is also visible here: https://angular.dev/ecosystem/service-workers

  20. angular-automatic-lock-bot commented on Sep 29, 2025

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  21. locked and limited conversation to collaborators on Sep 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: i18nIssues related to localization and internationalizationarea: service-workerIssues related to the @angular/service-worker packagefeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under consideration

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions