Skip to content
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

i18n: Support changing language inside the app #24549

Closed
HarelM opened this issue Jun 16, 2018 · 18 comments
Closed

i18n: Support changing language inside the app #24549

HarelM opened this issue Jun 16, 2018 · 18 comments

Comments

@HarelM
Copy link

HarelM commented Jun 16, 2018

I'm submitting a...


[x] Feature request

Current behavior

From the documentation:

You need to build and deploy a separate version of the app for each supported language.

Expected behavior

I would like to be able to dynamically change the language using i18n tool chain.

What is the motivation / use case for changing the behavior?

A user would like to change the language of the app without a full refresh to preserve current application state, much like other SPA actions.
Can be seen here (right side language button):
https://israelhiking.osm.org.il/
The above site that I created is using gettext which is an awesome tool but is not the angular team's choice and I prefer to use maintained tools and not deprecated ones.
Gettext can use a json file that was compiled from translations pot file and served as a static file to facilitate language changes.
IMHO, building a separate app for different languages is not the right approach, especially considering how SPA works.
Also #11405 is a key aspect here that can be solved with this approach.
I believe the current solution is not mature enough and I think it's worth considering other requirements.

Environment


Angular version: 6.0.5
Browser: All
@jnizet
Copy link
Contributor

jnizet commented Jun 16, 2018

The i18n team is already working on it: #16477

@HarelM
Copy link
Author

HarelM commented Jun 16, 2018

@jnizet Seem like it - couldn't find it when searching for it (title is misleading).
Feel free to close it, or use it as a placeholder for this development (I didn't see an issue reference for this subject in #16477 initial post)

@spock123
Copy link

@HarelM Just out of curiosity - how are you going to create language specific templates in the build step, without creating different versions of your application?

Are you talking about dynamic translation? Ngx-translate already has this.

For a production application, I'm not sure how you can avoid building separate apps for each language

@jnizet
Copy link
Contributor

jnizet commented Jun 16, 2018

@spock123 it's not possible yet, which is why this is a feature request, i.e. a request to provide a feature that doesn't exist yet. And, as I said, the NG team is working on it. So it will be possible to generate a single bundle supporting several languages.

@spock123
Copy link

@jnizet Thanks..

@ocombe
Copy link
Contributor

ocombe commented Jun 18, 2018

There are two different things here:

  • changing the language without reloading the app: this is not on the road map for now, given how i18n is deeply implemented into the view creations, it's simply not possible to change the language without recreating the templates
  • one bundle for all languages: this will be possible with the new rendering engine (ivy), translations will be applied at runtime when the view are created, which means that you can lazy load the locale file before the application starts

@ngbot ngbot bot added this to the needsTriage milestone Jun 18, 2018
@HarelM
Copy link
Author

HarelM commented Jun 18, 2018

My current solution that I'm using is to place all the text in a service and use it throughout the app:
<h1>{{tralations.word}}<h1>
When changing language I just update the variables in the translation service.
I'm guessing that without the above two abilities implemented there's no incentive for me to change how my app works...

@spock123
Copy link

@HarelM can I ask why you chose to implement what https://github.com/ngx-translate/core already dos? Just curious (sorry if off topic guys).

@HarelM
Copy link
Author

HarelM commented Jun 19, 2018

Well, basically when I started my project I used AngularJS and I didn't fancy the fact that I need to define magic IDs for every string that I use (personal preference), instead I use the string itself as the ID (this is how gettext works and one of the reasons I choose it).
All the tooling related to translation were already defined (which I'm not sure was the case when I review this topic for ngx-translate) and are now battle proof.
When I migrated to angular 2 I wrote a very small service to support it and it still serves me to this day so I didn't saw a reason to change it.
Changing all the tools and the way the site currently works is a hassle, but gettext is no longer supported for angular 2 and above - hence I'm here :-).
If I decide to change this infrastructure I would like to do it towards a library that will be maintained - I do have this confident for i18n but less with ngx-translate.
Having said that I need a library that supports my current needs which currently i18n doesn't.

@mattiLeBlanc
Copy link

I am having the same issue: One app running on one instance with two domains, AU and NZ.
When I move to NZ, I get a different locale.

I am looking at this example (https://embed.plnkr.co/lWV4VhzpWYnCXeDBpzsn/) to fetch the locale source file dynamically.

@mattiLeBlanc
Copy link

mattiLeBlanc commented Jul 3, 2018

I used the example mentioned in my previous post, but it didn't completely work because at the moment the getTranslationProviders was executed, the locale wasn't available yet.

I have a different variation of the solution and I would LOVE feedback on how to do it better.

Context: we have one app running on one instance, with two domain names pointing to it.
We have mydomain.com and mydomain.co.nz.

In the environment.ts file I have added a countryLookup key:

countryLookup: 
  'mydomain.com': 'au',
 'mydomain.co.nz': 'nz'
}

Then in my maint.ts I have

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { Location } from '@angular/common';
import { environment } from './environments/environment';
import { getTranslationProviders } from './app/providers/i18n.provider';
import { LocaleClass } from './app/providers/locale.provider';

if ( environment.production ) {
  enableProdMode();
}

const locale = new LocaleClass( Location );
locale.setLocale().then( ( localeValue ) => {
  getTranslationProviders( localeValue ).then( providers => {
    platformBrowserDynamic().bootstrapModule( AppModule, { providers } )
      .catch( err => console.log( err ) );
  } );
} );

The bit I am unsure of is the LocaleClass. This class does a lookup based on the URL and matches that with the countryLookup from the environment file. It then returns the correct locale and also sets it in a local variable and on the window scope:

import { Inject } from '@angular/core';
import { WindowRef } from 'app/services/windowRef.service';
import { environment } from 'environments/environment';
import { Location } from '@angular/common';

export class LocaleClass {
  locale: string;
  constructor( @Inject( Location ) private location ) {
  }

  setLocale(): Promise<string> {
    const noProviders: Object[] = [];
    const winRef = new WindowRef();
    this.locale = ( environment.countryLookup[ location.hostname ] ).toLowerCase();
    // store locale in document
    //
    winRef.nativeWindow.document.locale = this.locale;
    return Promise.resolve( this.locale );
  }

  getLocale(): string {
    return this.locale;
  }
}

Then finally the translation provider:

import { TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core';
declare const require;

export function getTranslationProviders( locale: string ): Promise<any[]> {

  // Get the locale id as arugment or from the global
  //
  const localeValue = locale || document[ 'locale' ] as string;


  // return no providers if fail to get translation file for locale
  //
  const noProviders: Object[] = [];

  // No locale or AU, doesn't require translation
  //
  if ( !localeValue || localeValue === 'au' ) {
    return Promise.resolve( noProviders );
  }

  try {
    const translations = require( `raw-loader!../../locale/messages.${ localeValue }.xlf` );
    return Promise.resolve( [
      { provide: TRANSLATIONS, useValue: translations },
      { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }
    ] );
  } catch ( error ) {
    console.error( error );
    return Promise.resolve( noProviders );
  }
}

So what I am not a 100% sure of is using that LocaleClass in the main.ts.
I tried for 2 hours to use the platformBrowserDynamic to accept a provider (factory and or class) and I couldn't make it fire.
I believe I can't add the provider to the bootstrapModule because when that providers executes, it already needs to know the locale.

So I ended up extracting the locale before bootstrapping the module. It feels a little bit finicky, so PLEASE suggest how to better this example.

edit
I already noticed that the injection of Location doesnt make sense in the main.ts. First of all, I shouldn't use Location, its not the document.location. If I insert DOCUMENT, I will just get the injectionCode in my function. So for now I am going to use document.location directly to get my stuff to work. So please help.

@mattiLeBlanc mattiLeBlanc mentioned this issue Jul 3, 2018
20 tasks
@ocombe
Copy link
Contributor

ocombe commented Jul 3, 2018

It seems ok for now, if you don't mind using JIT. We should have better tools to detect the locale and lazy load translations at bootstrap for ivy since we will do translations at runtime.
If it works for now, keep it like this and wait until we finish the i18n refactoring.

@mattiLeBlanc
Copy link

@ocombe Unfortunately it doesn't work when I build with --prod. Without --prod, the bundle files include the XML, but with the prod flag it doesn't include it.
I assume Require is a node command and works locally, but on the server where Apache or IIS is serving the app it doesn't.
Would system.js be able to load the XML files on demand?

Otherwise I am forced to create to build instances with separate languages :(

@mattiLeBlanc
Copy link

Okay, I figured out running --prod --aot=false will keep the webpack stuff required for the lazy loading of the XLF files. The payoff is a larger JS package file, but at least the i18n with dynamic language changing is working.

Good enough for now until there is a proper solution in A6.

@ocombe
Copy link
Contributor

ocombe commented Jul 7, 2018

what about --build-optimizer=false instead?

@mattiLeBlanc
Copy link

@ocombe Just tried that. With --build-optimizer=false I am looking at a larger main bundle (385kb) but a smaller vendor bundle (775kb). The i18n xml is included in the main, but the realtime language switching does not work (because webpack require was dropped by --prod?).

When I run ng build --env=dev --prod --aot=false, the main file is 263kb but the vendor file is 1.2mb and the language switching works.

In essence I have 4 options to go with:

  1. run without aot
  2. use 2 instances, one for each language and build with proper language file
  3. amalgamate language files into one json file and write my own directive that gets text from the json in realtime. (wouldn't that be as quick as what we use now with i18n?). If we can lazy load component, we would also be able to lazy load a language specific json on language change.
  4. there was a ngx-internationalization project I believe that might support language switching

@vicb
Copy link
Contributor

vicb commented Jul 10, 2018

closing as duplicate (#16477)

@vicb vicb closed this as completed Jul 10, 2018
@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants