I'm submitting a ... (check one with "x")
[X ] bug report => check the FAQ and search github for a similar issue or PR before submitting
[X ] support request => check the FAQ and search github for a similar issue before submitting
[X] feature request
Current behavior
To implement ngx-translate in a multilanguage app using angular universal, ngx-translate has to be initialized on the server side (using for example the request lang) and on the client side (using the browser language).
This result is the following glitchy side effect:
This effect/this refresh of the texts produce a quick glitch (a couple of ms) where use could notice that the texts disappear and appear again
Note: I tried to not initialize ngx-translate on the browser side but only on the server side. This actually result in the same glitch, except that after the glich, texts will just be rendered with their keys instead of their values (same effect as when ngx-translate isn't initialized)
Expected/desired behavior
I would love to solve this glitch respectively to don't have a visual glitch when ngx-translate is initialized on the browser side.
That may be a bug, a feature request or simply something I'm doing wrong and in such a case I would love to have support because I wasn't able to solve it since months.
Reproduction of the problem
You could try this behavior with an incognito browser and my website https://fuster.io
You could reproduce it so many times you want as long as you clean your storage first (or close/open a new incognito tab)
Here also a video to demonstrate the glitch with my angular universal website app https://www.dropbox.com/s/5y8zdxryw50fcf2/ngx-translate-glitch.mov?dl=0
What is the motivation / use case for changing the behavior?
Would be cleaner and a better experience for the users.
Please tell us about your environment:
ngx-translate version:
"ngx-translate/core": "^9.0.2",
"ngx-translate/http-loader": "^2.0.0",
Angular version:
"angular/core": "^5.1.1"
Angular universal:
My project is an up-to-date version of the https://github.com/angular/universal-starter
I you would like me to create a dummy GitHub repo, just let me know
Code:
Just in case, here how I initialized on the server and browser side ngx-translate:
private initLang(): Observable<Object> {
this.translateService.addLangs(['en', 'de', 'fr', 'it']);
this.translateService.setDefaultLang('en');
if (isPlatformServer(this.platformId)) {
let acceptLanguage: string = this.req.headers['accept-language'];
if (Comparator.isStringEmpty(acceptLanguage)) {
return this.translateService.use('en');
} else {
let languages: string[] = acceptLanguage.match(/[a-zA-Z\-]{2,10}/g) || [];
if (languages.length > 0) {
let userLang: string = languages[0].split('-')[0];
userLang = /(de|en|fr|it)/gi.test(userLang) ? userLang : 'en';
return this.translateService.use(userLang);
} else {
return this.translateService.use('en');
}
}
} else {
const lang: string = this.translateService.getBrowserLang();
const match = (lang || '').match(/(en|de|fr|it)/);
return this.translateService.use(match ? match[0] : 'en');
}
}
This is an issue with angular universal I think, they haven't implemented the state transfer yet (with rehydration), which means that when the app bootstraps it will do all the work on top of the existing DOM.
Once the state transfer exists, the app should start already bootstrapped and you shouldn't see this glitch.
A few things that you can try to improve that: use the TransferHttpCacheModule to have an immediate loading of the json files (if you use the http loader) https://github.com/angular/universal/tree/master/modules/common
Try to implement something like ngx-cache: https://github.com/fulls1z3/ngx-cache I haven't tested it myself, but it seems to do the state transfer
thx a lot @ocombe for the explanation and answer, merci beaucoup
I have now implemented TransferHttpCacheModule (I should have done it earlier, I was still using the "old" transfer-state module). Looks a bit better (glitch is maybe faster) but still glitchy ;)
Do you know if there is an issue/feature request about state transfer and rehydratation I would be able to follow?
Even if it's not a ngx-translate issue, is it ok if I let the issue open till solved (it may interest other persons)?
Apparently the state transfer already exists: https://angular.io/api/platform-browser/TransferState
But the dom hydration not yet, you can follow the status here: https://github.com/angular/angular/issues/13446
@ocombe yep, exactly, I am now implementing the the state transfer too ;)
It's also a bit glitchy (see for example https://fluster.io/item/3P5KpjA7PMSyM73MvnBM) but don't know if it's related to the dom hydration too, let's see
thx for pointing the issue, I gonna follow it. if you've got other idea regarding this subject, I won't be against of course 馃槈
et joyeux No毛l 馃巹馃槂
you can probably use the state transfer to transfer the state of ngx translate using getTranslation / setTranslation
I confirm that it works now like a charm, therefore I gonna summarized here the solution and close my issue.
PRECONDITION
This issue applies if you are using angular universal to build a multilanguage website and if you are facing a glitch while the app is rendered in the browser.
The source of the problem is that actually state transfer doesn't implement yet DOM rehydration (see https://github.com/angular/angular/issues/13446).
To solve this or to bypass this problem, the solution is to load and use ngx-translate on both server and browser side with custom loaders. The idea is to load the translations on the server side and to pass them to the browser side within the help of the transfer-state.
HOW TO
Cache your http request within the use of TransferHttpCacheModule https://github.com/angular/universal/tree/master/modules/common
Use transfer-state to pass data from your server to your browser https://angular.io/api/platform-browser/TransferState
Create a loader for the server side translate-server-loader.service.ts
import {Observable} from "rxjs/Observable";
import {TranslateLoader} from '@ngx-translate/core';
declare var require: any;
import {join} from 'path';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
const fs = require('fs');
export class TranslateServerLoader implements TranslateLoader {
constructor(private prefix: string = 'i18n',
private suffix: string = '.json',
private transferState: TransferState) {
}
public getTranslation(lang: string): Observable<any> {
return Observable.create(observer => {
const assets_folder = join(process.cwd(), 'dist', 'server', this.prefix);
const jsonData = JSON.parse(fs.readFileSync(`${assets_folder}/${lang}${this.suffix}`, 'utf8'));
// Here we save the translations in the transfer-state
const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
this.transferState.set(key, jsonData);
observer.next(jsonData);
observer.complete();
});
}
}
Use the server loader in your app.server.module.ts
export function translateFactory(transferState: TransferState) {
return new TranslateServerLoader('/assets/i18n', '.json', transferState);
}
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: translateFactory,
deps: [TransferState]
}
})
Create a loader for the browser side translate-browser-loader.service.ts
import {Observable} from "rxjs/Observable";
import {TranslateLoader} from '@ngx-translate/core';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient} from '@angular/common/http';
export class TranslateBrowserLoader implements TranslateLoader {
constructor(private prefix: string = 'i18n',
private suffix: string = '.json',
private transferState: TransferState,
private http: HttpClient) {
}
public getTranslation(lang: string): Observable<any> {
const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
const data = this.transferState.get(key, null);
// First we are looking for the translations in transfer-state, if none found, http load as fallback
if (data) {
return Observable.create(observer => {
observer.next(data);
observer.complete();
});
} else {
return new TranslateHttpLoader(this.http, this.prefix, this.suffix).getTranslation(lang);
}
}
}
Use the server loader in your app.browser.module.ts
export function exportTranslateStaticLoader(http: HttpClient, transferState: TransferState) {
return new TranslateBrowserLoader('/assets/i18n/', '.json', transferState, http);
}
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: exportTranslateStaticLoader,
deps: [HttpClient, TransferState]
}
}
)
I've now deployed this solution in production, if you want to have a look, https://fluster.io
I really would like to thank you @ocombe for your support, I'm so happy, this is so neat without glitch, you are really awesome 馃憤
This. Took me days to get this to work until I found this issue. Any way those loaders make their way into ngx-translate?
There was just one thing I had to change, which was that ES6 imports for path didn't work and I had to require it. Same for process.cwd(), I'm relying on the relative build path now. Maybe a module config I'm missing?
Anyway, cheers @peterpeterparker for posting this!
Thank you @peterpeterparker ! Same as @freezy, typescript error with path and process. Could you share with us your config? Cheers
@stephanegg sure I will, just tell me, which config are you interested in? package.json or tsconfig.json or somehing else?
I would say all your config, ie package.json, tsconfig.json, tsconfig.app.json, tsconfig.server.json and webpack.config.js to be sure to spot the difference. Or if it's easier for you, a repo with universal ngx translate working. I am sure, it will help a lot of people.
Thanks again @peterpeterparker
@stephanegg about repo, no magic, I'm just up-to-date with angular universal starter app repo https://github.com/angular/universal-starter
about the files, package.json
"dependencies": {
....
"@angular/animations": "^5.1.1",
"@angular/cdk": "^5.0.1",
"@angular/common": "^5.1.1",
"@angular/compiler": "^5.1.1",
"@angular/core": "^5.1.1",
"@angular/forms": "^5.1.1",
"@angular/http": "^5.1.1",
"@angular/material": "^5.0.1",
"@angular/platform-browser": "^5.1.1",
"@angular/platform-browser-dynamic": "^5.1.1",
"@angular/platform-server": "^5.1.1",
"@angular/router": "^5.1.1",
"@nguniversal/common": "^5.0.0-beta.5",
"@nguniversal/express-engine": "^5.0.0-beta.5",
"@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5",
"@ngx-translate/core": "^9.0.2",
"@ngx-translate/http-loader": "^2.0.0",
....
"rxjs": "^5.5.5",
"serialize-javascript": "^1.4.0",
...
"zone.js": "^0.8.18"
},
"devDependencies": {
"@angular/cli": "^1.6.1",
"@angular/compiler-cli": "^5.1.1",
"@angular/language-service": "^5.1.1",
"@types/node": "^8.5.1",
"cpy-cli": "^1.0.1",
"http-server": "^0.10.0",
"reflect-metadata": "^0.1.10",
"ts-loader": "^3.2.0",
"typescript": "^2.6.2"
...
}
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
}
}
tsconfig.app.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015"
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
tsconfig.server.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
// Set the module format to "commonjs":
"module": "commonjs"
},
"exclude": [
"test.ts",
"**/*.spec.ts"
],
// Add "angularCompilerOptions" with the AppServerModule you wrote
// set as the "entryModule".
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}
no webpack.config.js but webpack.server.config.js, same as the above mentioned repo
I hope this help
Thank you ! It's working fine now however I had to change TranslateModule.forChild({...}) to TranslateModule.forRoot({...}) in app.browser.module.ts to solve a NullInjectorError: No provider for TranslateStore! issue.
This issue should get the FAQ label. Cheers
Perhaps I missed something but I don't really see the point of using 2 separates loader since we can easily know if we are on client or server side.
here is my I18nModule (imported only by the app.module.ts since this one is imported by the server module)
import { NgModule, InjectionToken, Optional, PLATFORM_ID } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
import { TranslateModule, TranslateService, TranslateLoader } from "@ngx-translate/core";
import { TranslateUniversalLoader } from "./universal.loader";
import { TransferState } from "@angular/platform-browser";
import { isPlatformServer } from "@angular/common";
const translateLoader = (transferState: TransferState, plateformId: object) => {
return new TranslateUniversalLoader(transferState, isPlatformServer(plateformId));
};
@NgModule({
imports: [
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: translateLoader,
deps: [TransferState, PLATFORM_ID]
}
}),
],
exports: [TranslateModule]
})
export class I18Module {
constructor(translate: TranslateService) {
translate.setDefaultLang('en');
}
}
here is the implementation of the TranslateUniversalLoader who will in the server side load the json and register it in the translateState and use it to translate the rendered html.
In the browser side will simply read the value from the transfertState
import { TranslateLoader } from "@ngx-translate/core";
import { Observable } from "rxjs/Observable";
import * as fs from 'fs';
import { makeStateKey, StateKey, TransferState } from '@angular/platform-browser';
import { isPlatformServer } from "@angular/common/src/platform_id";
const key: StateKey<number> = makeStateKey<number>('transfer-translate');
export class TranslateUniversalLoader implements TranslateLoader {
constructor(private transferState: TransferState, private isServer: boolean) {
console.log('server' + isServer);
}
public getTranslation(lang: string): Observable<any> {
return Observable.create(observer => {
if (this.isServer) {
let json = JSON.parse(fs.readFileSync(`assets/i18n/${lang}.json`, 'utf8'));
this.transferState.set(key, json);
}
observer.next(this.transferState.get(key, null));
observer.complete();
});
}
}
I actually can't see any glitch. Hop it can help
I've switched to custom JSON module loader, it solved the problem for my angular 5 app:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import * as translationEn from 'assets/i18n/en.json';
import * as translationRu from 'assets/i18n/ru.json';
const TRANSLATIONS = {
en: translationEn,
ru: translationRu,
};
class JSONModuleLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return Observable.of(TRANSLATIONS[lang]);
}
}
export function JSONModuleLoaderFactory() {
return new JSONModuleLoader();
}
typings.d.ts:
declare module '*.json' {
const value: any;
export default value;
}
cool @xrobert35 I'm agree with you, it's easier to have only one loader, more handy.
But, regarding my original issue, something doesn't work with your code, it doesn't handle the case where the user load the app in a language and decide to change it afterwards dynamically.
Therefore I have made a small enhancement to your code:
import {isPlatformServer} from '@angular/common';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser'
import {HttpClient} from '@angular/common/http';
import {TranslateLoader} from "@ngx-translate/core";
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {Observable} from "rxjs/Observable";
declare var require: any;
const fs = require('fs');
import {join} from "path";
export function translateFactory(transferState: TransferState, http: HttpClient, plateformId: object) {
return new TranslateUniversalLoader(isPlatformServer(plateformId), '/assets/i18n/', '.json', transferState, http);
}
export class TranslateUniversalLoader implements TranslateLoader {
constructor(private isServer: boolean,
private prefix: string = 'i18n',
private suffix: string = '.json',
private transferState: TransferState,
private http: HttpClient) {
}
public getTranslation(lang: string): Observable<any> {
const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
const data = this.transferState.get(key, null);
if (!data && !this.isServer) {
return new TranslateHttpLoader(this.http, this.prefix, this.suffix).getTranslation(lang);
} else {
return Observable.create(observer => {
if (this.isServer) {
const assets_folder = join(process.cwd(), 'dist', 'server', this.prefix);
const jsonData = JSON.parse(fs.readFileSync(`${assets_folder}${lang}${this.suffix}`, 'utf8'));
this.transferState.set(key, jsonData);
}
observer.next(this.transferState.get(key, null));
observer.complete();
});
}
}
}
Related to this issue but not to ngx-translate. If like me you notice that your lazy loaded modules in your angular universal app are still glitchy even after having fix your ngx-translate loaders, you might need to add a configuration option { initialNavigation: 'enabled' } to your RouterModule.forRoot
Reference: https://github.com/angular/angular/issues/15716 see the @Owain94 awesome answer
For me you should not use TranslateHttpLoader.
If your client want to change the langage, it's the server job to give him the page new in the new langage.
Your request should contain the langage the client want to use.
You could get the language by using Injector :
Angular side :
const request = this.injector.get('request') || {};
const lang = request.lang;
translate.setDefaultLang(lang);
Node Side :
const beforeRender = (req, res, next) => {
//Get the client lang from the request
req.lang = getLang(req);
next();
};
app.get('/views/*', beforeRender,
(req, res) => {
res.render('index', { req, res });
});
@xrobert35 well, what you describe was my prior solution but I've to say I rather like the current one, a way more user friendly behavior, in my point of view at least
but we don't have to be agree, both solution are cool ;)
Hello! i have one problem! help me!
npm run build
ERROR in : Can't resolve all parameters for TranslateBrowserLoader in /home/carlos/Proyectos/website/src/app/translate-browser-loader.service.ts: (?, ?, [object Object], [object Object]).
Hi,
without more information about the code it will not be simple.
hI @xrobert35 I leave you all the settings
I am configuring ngx-translate as follows:
translate-server-loader.service.ts
import {Observable} from "rxjs/Observable";
import {TranslateLoader} from '@ngx-translate/core';
import { Injectable } from '@angular/core';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient} from '@angular/common/http';
@Injectable()
export class TranslateBrowserLoader implements TranslateLoader {
constructor(private prefix: string = 'i18n',
private suffix: string = '.json',
private transferState: TransferState,
private http: HttpClient) {
}
public getTranslation(lang: string): Observable<any> {
const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
const data = this.transferState.get(key, null);
// First we are looking for the translations in transfer-state, if none found, http load as fallback
if (data) {
return Observable.create(observer => {
observer.next(data);
observer.complete();
});
} else {
return new TranslateHttpLoader(this.http, this.prefix, this.suffix).getTranslation(lang);
}
}
}
src/app/app.server.module.ts
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import {HttpClientModule, HttpClient} from '@angular/common/http';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
import {TranslateServerLoader} from './translate-server-loader.service';
@NgModule({
imports: [
AppModule,
ServerModule,
ServerTransferStateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: translateFactory,
deps: [TransferState]
}
}),
TranslateHttpLoader
],
bootstrap: [AppComponent],
providers:[TranslateHttpLoader]
})
export class AppServerModule { }
export function translateFactory(transferState: TransferState) {
return new TranslateServerLoader(transferState);
}
translate-browser-loader.service.ts
import {Observable} from "rxjs/Observable";
import {TranslateLoader} from '@ngx-translate/core';
import { Injectable } from '@angular/core';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient} from '@angular/common/http';
@Injectable()
export class TranslateBrowserLoader implements TranslateLoader {
constructor(private prefix: string = 'i18n',
private suffix: string = '.json',
private transferState: TransferState,
private http: HttpClient) {
}
public getTranslation(lang: string): Observable<any> {
const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
const data = this.transferState.get(key, null);
// First we are looking for the translations in transfer-state, if none found, http load as fallback
if (data) {
return Observable.create(observer => {
observer.next(data);
observer.complete();
});
} else {
return new TranslateHttpLoader(this.http, this.prefix, this.suffix).getTranslation(lang);
}
}
}
app.module.ts
import { BrowserModule, BrowserTransferStateModule, TransferState } from '@angular/platform-browser';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { BetsComponent } from './bets/bets.component';
import { BetsEventComponent } from './bets-event/bets-event.component';
import { CountriesComponent } from './countries/countries.component';
import { InformationComponent } from './information/information.component';
import { JackpotComponent } from './jackpot/jackpot.component';
import { PromotionsComponent } from './promotions/promotions.component';
import { ResultComponent } from './result/result.component';
import { PromotionDetailsComponent } from './promotion-details/promotion-details.component';
import { TopFootballComponent } from './top-football/top-football.component';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { ClipboardModule } from 'ngx-clipboard';
import {TooltipModule} from 'primeng/primeng';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import {TranslateBrowserLoader} from './translate-browser-loader.service';
import {TranslateServerLoader} from './translate-server-loader.service';
import {
MenuLeftComponent,
FooterComponent,
HeaderComponent,
BetslipComponent,
MenuBottomMobileComponent,
SharedModule,
FixtureService,
BetslipService,
TypeBetsService,
TicketService,
TimeService,
PaginationService,
TranslateTbet,
CompetitionService,
ResultService,
NumberOnlyDirective
} from './shared';
import {
CashierComponent,
InPlayComponent,
InboxComponent,
LoginComponent,
ProfileComponent,
RegisterComponent,
TicketsComponent,
TicketWinLoseComponent,
BetslipMobileComponent,
CheckTicketsComponent,
ContactUsComponent,
CollapseComponent,
CollapseModule,
} from './collapse';
import { CompetitionsComponent } from './competitions/competitions.component';
const rootRouting: ModuleWithProviders = RouterModule.forRoot([
{path:'',component:HomeComponent},
{path:'competitions/:id', component: CompetitionsComponent},
{path:'match-bets/:idFixture', component: BetsEventComponent},
{path:'results', component: ResultComponent},
{path:'results/:nameLeague', component: ResultComponent},
{path:'results/:nameLeague/:dateTime', component: ResultComponent},
{path:'jackPot', component: JackpotComponent},
{path:'promotions', component: PromotionsComponent},
{path:'information/:idInformation', component:InformationComponent},
{path:'promotionDetails', component: PromotionDetailsComponent},
{path:'topFootball', component: TopFootballComponent},
{path:'countries', component: CountriesComponent},
{path:':id', component: BetsComponent},
], { useHash: false });
@NgModule({
declarations: [
AppComponent,
HomeComponent,
BetsComponent,
BetsEventComponent,
CountriesComponent,
InformationComponent,
JackpotComponent,
PromotionsComponent,
ResultComponent,
PromotionDetailsComponent,
TopFootballComponent,
HeaderComponent,
FooterComponent,
MenuLeftComponent,
BetslipComponent,
MenuBottomMobileComponent,
TicketWinLoseComponent,
InPlayComponent,
TicketsComponent,
CheckTicketsComponent,
LoginComponent,
InboxComponent,
RegisterComponent,
CashierComponent,
ProfileComponent,
ContactUsComponent,
BetslipMobileComponent,
CollapseComponent,
TranslateTbet,
NumberOnlyDirective,
CompetitionsComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
rootRouting,
SharedModule,
CollapseModule,
HttpModule,
HttpClientModule,
BrowserTransferStateModule,
BrowserModule.withServerTransition({
appId: 'ng-universal-demystified'
}),
ClipboardModule,
TooltipModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: exportTranslateStaticLoader,
deps: [HttpClient, TransferState]
}
}
)
],
providers: [
FixtureService,
BetslipService,
TypeBetsService,
TicketService,
TimeService,
PaginationService,
TranslateTbet,
NumberOnlyDirective,
CompetitionService,
ResultService,
TransferState,
TranslateModule,
TranslateBrowserLoader,
TranslateServerLoader,
TranslateHttpLoader
],
bootstrap: [AppComponent]
})
export class AppModule { }
export function exportTranslateStaticLoader(http: HttpClient, transferState: TransferState) {
return new TranslateBrowserLoader(transferState, http);
}
after compiling with the command:
npm run build
And it gives the following error:
**ERROR in : Can't resolve all parameters for TranslateHttpLoader in /home/carlos/Proyectos/website/node_modules/@ngx-translate/http-loader/ngx-translate-http-loader.d.ts: ([object Object], ?, ?).**
please help!
@ceaguilera are you using Angular v5 and ngx-translate v10?
If yes, maybe it's the same problem as described in the README of https://github.com/ngx-translate/core
@ceaguilera why are you trying to import or provide TranslateHttpLoader ? it's just a simple class not an injectable or a module, remove those declaration from the modules AppModule and AppServerModule
Thank @peterpeterparker for response, however the error persists degrading the version of ngx-translate to version 9, I leave my package.json:
{
"name": "universal-demo-v5",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "node dist/server",
"build": "run-s build:client build:aot build:server",
"build:client": "ng build --prod --app 0",
"build:aot": "ng build --aot --app 1",
"build:server": "webpack -p",
"build:dev": "run-p build:dev:client build:dev:aot build:dev:server",
"build:dev:client": "ng build -w --aot --app 0",
"build:dev:aot": "ng build -w --aot --delete-output-path=false --app 1",
"build:dev:server": "webpack -w",
"nodemon": "sleep 25 && nodemon --watch dist/browser/index.html --watch dist/server.js dist/server.js",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"postinstall": "npm run build",
"start:heroku": "node dist/server"
},
"private": true,
"dependencies": {
"@angular-devkit/schematics": "0.0.37",
"@angular/animations": "^5.0.0",
"@angular/common": "^5.0.0",
"@angular/compiler": "^5.0.0",
"@angular/core": "^5.0.0",
"@angular/forms": "^5.0.0",
"@angular/http": "^5.2.8",
"@angular/platform-browser": "^5.0.0",
"@angular/platform-browser-dynamic": "^5.0.0",
"@angular/platform-server": "^5.0.0",
"@angular/router": "^5.0.0",
"@ngx-translate/core": "9.1.1",
"@ngx-translate/http-loader": "^3.0.1",
"bootstrap": "4.0.0-alpha.6",
"compression": "^1.7.2",
"core-js": "^2.5.3",
"express": "^4.16.2",
"flag-icon-css": "^3.0.0",
"font-awesome": "^4.7.0",
"jquery": "^3.3.1",
"ngx-clipboard": "^10.0.0",
"ngx-window-token": "0.0.4",
"primeng": "^5.0.2",
"primer-tooltips": "^1.5.2",
"rxjs": "^5.5.6",
"tether": "^1.2.4",
"zone.js": "^0.8.5"
},
"devDependencies": {
"@angular/cli": "1.6.1",
"@angular/compiler-cli": "^5.0.0",
"@angular/language-service": "^5.0.0",
"@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~9.4.6",
"codelyzer": "~4.2.1",
"jasmine-core": "~3.1.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.4.2",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"nodemon": "^1.17.1",
"npm-run-all": "^4.1.2",
"protractor": "~5.3.0",
"ts-loader": "^3.5.0",
"ts-node": "~5.0.1",
"tslint": "~5.9.1",
"typescript": "^2.5.3",
"webpack-node-externals": "^1.6.0"
}
}
It turns out that the solution I described above isn't compatible with Angular v6 anymore because fs will not be recognize and allowed at compilation time by webpack. Furthermore, since the webpack config couldn't be ejected anymore, there is no way to tell webpack to ignore fs and path
Anyway, the solution described above by @xuhcc do works like a charm with Angular v6, kudos!
It looks like the following then:
translate-universal-loader.service.ts
import {TranslateLoader} from '@ngx-translate/core';
import {Observable, of} from 'rxjs';
import * as translationEn from 'assets/i18n/en.json';
import * as translationFr from 'assets/i18n/fr.json';
import * as translationDe from 'assets/i18n/de.json';
import * as translationIt from 'assets/i18n/it.json';
const TRANSLATIONS = {
en: translationEn,
fr: translationFr,
de: translationDe,
it: translationIt
};
export class TranslateUniversalLoader implements TranslateLoader {
constructor() {
}
public getTranslation(lang: string): Observable<any> {
return of(TRANSLATIONS[lang]);
}
}
export function translateFactory() {
return new TranslateUniversalLoader();
}
modify the load which call translateFactory to reflect the changes
loader: {
provide: TranslateLoader,
useFactory: translateFactory
}
create a new file called typings.d.ts under src:
declare module '*.json' {
const value: any;
export default value;
}
in ./tsconfig.json modify typeRoots
"typeRoots": [
"node_modules/@types", "./src/typings.d.ts"
]
IMPORTANT NOTE It looks like the assets aren't copied anymore on the server side. In angular.json, the config angular-devkit/build-angular:server even doesn't support assets right now. Therefore you will have to copy your assets language files manually to dist folder
I've switched to custom JSON module loader, it solved the problem for my angular 5 app:
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import * as translationEn from 'assets/i18n/en.json'; import * as translationRu from 'assets/i18n/ru.json'; const TRANSLATIONS = { en: translationEn, ru: translationRu, }; class JSONModuleLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return Observable.of(TRANSLATIONS[lang]); } } export function JSONModuleLoaderFactory() { return new JSONModuleLoader(); }typings.d.ts:
declare module '*.json' { const value: any; export default value; }
Hello, I followed your instructions and made the necessary changes but still, it is not working. Do I need to add anything else? I integrated your code into my angular 7 application. When viewing the page source I'm not able to see the text from JSON files.
Hi @GudaPraveenKumar
I also have JSONModuleLoaderFactory applied in the main app module:
@NgModule({
//
imports: [
//
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: JSONModuleLoaderFactory,
deps: []
}
})
]
})
Don't know whether it works with Angular 7 or not.
Hi @GudaPraveenKumar
I also have
JSONModuleLoaderFactoryapplied in the main app module:@NgModule({ // imports: [ // TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: JSONModuleLoaderFactory, deps: [] } }) ] })Don't know whether it works with Angular 7 or not.
Even I did the same but still no results :(
It turns out that the solution I described above isn't compatible with Angular v6 anymore because
fswill not be recognize and allowed at compilation time by webpack. Furthermore, since the webpack config couldn't be ejected anymore, there is no way to tell webpack to ignorefsandpathAnyway, the solution described above by @xuhcc do works like a charm with Angular v6, kudos!
It looks like the following then:
translate-universal-loader.service.ts
import {TranslateLoader} from '@ngx-translate/core'; import {Observable, of} from 'rxjs'; import * as translationEn from 'assets/i18n/en.json'; import * as translationFr from 'assets/i18n/fr.json'; import * as translationDe from 'assets/i18n/de.json'; import * as translationIt from 'assets/i18n/it.json'; const TRANSLATIONS = { en: translationEn, fr: translationFr, de: translationDe, it: translationIt }; export class TranslateUniversalLoader implements TranslateLoader { constructor() { } public getTranslation(lang: string): Observable<any> { return of(TRANSLATIONS[lang]); } } export function translateFactory() { return new TranslateUniversalLoader(); }- modify the load which call
translateFactoryto reflect the changes
loader: { provide: TranslateLoader, useFactory: translateFactory }- create a new file called
typings.d.tsunder src:
declare module '*.json' { const value: any; export default value; }- in
./tsconfig.jsonmodifytypeRoots
"typeRoots": [ "node_modules/@types", "./src/typings.d.ts" ]IMPORTANT NOTE It looks like the
assetsaren't copied anymore on the server side. Inangular.json, the configangular-devkit/build-angular:servereven doesn't support assets right now. Therefore you will have to copy your assets language files manually to dist folder
It is not working for me I integrated this in angular 7 application.
It is not working for me I integrated this in angular 7 application.
The way I found it to work in Angular 7:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateUniversalLoader
}
}),
import { TranslateLoader } from '@ngx-translate/core';
import { Observable, of } from 'rxjs';
import * as contentEn from './en.json';
import * as contentRo from './ro.json';
const TRANSLATIONS = {
en: contentEn,
ro: contentRo
};
export class TranslateUniversalLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return of(TRANSLATIONS[lang].default);
}
}
It looks like the returned object is added under the default key
tested this in Angular 7 and also tested dynamic change works just fine
It turns out that the solution I described above isn't compatible with Angular v6 anymore because
fswill not be recognize and allowed at compilation time by webpack. Furthermore, since the webpack config couldn't be ejected anymore, there is no way to tell webpack to ignorefsandpathAnyway, the solution described above by @xuhcc do works like a charm with Angular v6, kudos!
It looks like the following then:
1. translate-universal-loader.service.ts ``` import {TranslateLoader} from '@ngx-translate/core'; import {Observable, of} from 'rxjs'; import * as translationEn from 'assets/i18n/en.json'; import * as translationFr from 'assets/i18n/fr.json'; import * as translationDe from 'assets/i18n/de.json'; import * as translationIt from 'assets/i18n/it.json'; const TRANSLATIONS = { en: translationEn, fr: translationFr, de: translationDe, it: translationIt }; export class TranslateUniversalLoader implements TranslateLoader { constructor() { } public getTranslation(lang: string): Observable<any> { return of(TRANSLATIONS[lang]); } } export function translateFactory() { return new TranslateUniversalLoader(); } ``` 2. modify the load which call `translateFactory` to reflect the changes ``` loader: { provide: TranslateLoader, useFactory: translateFactory } ``` 3. create a new file called `typings.d.ts` under src: ``` declare module '*.json' { const value: any; export default value; } ``` 4. in `./tsconfig.json` modify `typeRoots` ``` "typeRoots": [ "node_modules/@types", "./src/typings.d.ts" ] ```IMPORTANT NOTE It looks like the
assetsaren't copied anymore on the server side. Inangular.json, the configangular-devkit/build-angular:servereven doesn't support assets right now. Therefore you will have to copy your assets language files manually to dist folder
This solution worked perfectly for my use case. Thank you 馃憤
Thanks to @xuhcc
import * as translationEn from '../../../lang-files/en.json';
import * as translationBn from '../../../lang-files/bn.json';
const TRANSLATIONS = {
en: translationEn,
bn: translationBn
};
export class CustomTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return of(TRANSLATIONS[lang]);
}
}
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: CustomTranslateLoader
}
});
This is complete code..
One thing to keep in mind use useClass insted of useFactory in loader config as show in last step.
Does anyone know how it should be done if I need to take the translations from a server? (I don't have them locally)
@peterpeterparker
I tried your given solution (posted on Dec 22-2017 )for Angular 6 and ending with the following error.
Does anyone has solution for this?
main.1f8b2b5261f163bcdf04.js:1 ERROR Error: Uncaught (in promise): Error: StaticInjectorError(Fs)[Lf -> t]:
StaticInjectorError(Platform: core)[Lf -> t]:
NullInjectorError: No provider for t!
Error: StaticInjectorError(Fs)[Lf -> t]:
StaticInjectorError(Platform: core)[Lf -> t]:
NullInjectorError: No provider for t!
at t.get (main.1f8b2b5261f163bcdf04.js:1)
at main.1f8b2b5261f163bcdf04.js:1
at t (main.1f8b2b5261f163bcdf04.js:1)
at t.get (main.1f8b2b5261f163bcdf04.js:1)
at main.1f8b2b5261f163bcdf04.js:1
at t (main.1f8b2b5261f163bcdf04.js:1)
at t.get (main.1f8b2b5261f163bcdf04.js:1)
at Ko (main.1f8b2b5261f163bcdf04.js:1)
at main.1f8b2b5261f163bcdf04.js:1
at $o (main.1f8b2b5261f163bcdf04.js:1)
at j (polyfills.7fb637d055581aa28d51.js:1)
at j (polyfills.7fb637d055581aa28d51.js:1)
at polyfills.7fb637d055581aa28d51.js:1
at e.invokeTask (polyfills.7fb637d055581aa28d51.js:1)
at Object.onInvokeTask (main.1f8b2b5261f163bcdf04.js:1)
at e.invokeTask (polyfills.7fb637d055581aa28d51.js:1)
at t.runTask (polyfills.7fb637d055581aa28d51.js:1)
at d (polyfills.7fb637d055581aa28d51.js:1)
Thank you,
Nilam
I'm pasting my solution here, it's similar to @xuhcc 's one, but on the frontend it will use the normal HttpLoader
import { TranslateLoader } from '@ngx-translate/core';
import * as translationEn from './assets/i18n/en.json';
import * as translationEs from './assets/i18n/es.json';
import { Observable, of } from 'rxjs/index';
import { isPlatformBrowser } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
const TRANSLATIONS = {
en: translationEn,
es: translationEs,
};
export class JSONModuleLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return of(TRANSLATIONS[lang]);
}
}
export function JSONModuleLoaderFactory(http: HttpClient, platform) {
if (isPlatformBrowser(platform)) {
return new TranslateHttpLoader(http);
} else {
return new JSONModuleLoader();
}
}
And in modules
import { PLATFORM_ID } from '@angular/core';
...
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: JSONModuleLoaderFactory,
deps: [HttpClient, PLATFORM_ID]
}
}),
I'm pasting my solution here, it's similar to @xuhcc 's one, but on the frontend it will use the normal HttpLoader
import { TranslateLoader } from '@ngx-translate/core'; import * as translationEn from './assets/i18n/en.json'; import * as translationEs from './assets/i18n/es.json'; import { Observable, of } from 'rxjs/index'; import { isPlatformBrowser } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; const TRANSLATIONS = { en: translationEn, es: translationEs, }; export class JSONModuleLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return of(TRANSLATIONS[lang]); } } export function JSONModuleLoaderFactory(http: HttpClient, platform) { if (isPlatformBrowser(platform)) { return new TranslateHttpLoader(http); } else { return new JSONModuleLoader(); } }And in modules
import { PLATFORM_ID } from '@angular/core'; ... TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: JSONModuleLoaderFactory, deps: [HttpClient, PLATFORM_ID] } }),
@aescarcha
"And in modules" >>> what modules do you mean? AppModule?, AppServerModule? or AppBrowserModule?
where could I use this:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: JSONModuleLoaderFactory,
deps: [HttpClient, PLATFORM_ID]
}
}),
I'm pasting my solution here, it's similar to @xuhcc 's one, but on the frontend it will use the normal HttpLoader
import { TranslateLoader } from '@ngx-translate/core'; import * as translationEn from './assets/i18n/en.json'; import * as translationEs from './assets/i18n/es.json'; import { Observable, of } from 'rxjs/index'; import { isPlatformBrowser } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; const TRANSLATIONS = { en: translationEn, es: translationEs, }; export class JSONModuleLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return of(TRANSLATIONS[lang]); } } export function JSONModuleLoaderFactory(http: HttpClient, platform) { if (isPlatformBrowser(platform)) { return new TranslateHttpLoader(http); } else { return new JSONModuleLoader(); } }And in modules
import { PLATFORM_ID } from '@angular/core'; ... TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: JSONModuleLoaderFactory, deps: [HttpClient, PLATFORM_ID] } }),@aescarcha
"And in modules" >>> what modules do you mean? AppModule?, AppServerModule? or AppBrowserModule?
where could I use this:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: JSONModuleLoaderFactory,
deps: [HttpClient, PLATFORM_ID]
}
}),
@ahmed-beheiry in the AppModule and any other modules you have. If you want to inherit the translations in you'll need to use forChild instead of forRoot
Related to this issue but not to ngx-translate. If like me you notice that your lazy loaded modules in your angular universal app are still glitchy even after having fix your ngx-translate loaders, you might need to add a configuration option
{ initialNavigation: 'enabled' }to yourRouterModule.forRootReference: angular/angular#15716 see the @Owain94 awesome answer
This comment saved long hours of research.
Working for lazy-loaded modules on Angular 7.
You'll need to set useClass: TranslateUniversalLoader on app-server.module
In the following article this solution, very simple and functional.
https://itnext.io/angular-universal-how-to-add-multi-language-support-68d83f6dfc4d
Remember that if you use translations within guards or resolvers you must start the translate service in these, since they are executed before the app.component.
In the following article this solution, very simple and functional.
https://itnext.io/angular-universal-how-to-add-multi-language-support-68d83f6dfc4dRemember that if you use translations within guards or resolvers you must start the translate service in these, since they are executed before the app.component.
That solution works, but does not prevent the "flickering" glitch.
Any update how to prevent the "flickering" ?
mabye it's not for this, but adding preboot prevented the text translation flickering, though introduced a style flickering, which has a workaround (here: https://github.com/angular/preboot/issues/75), though that doesn't work for me in angular 9. Might help some of you maybe.
any working solutions?
@xuhcc @peterpeterparker
I've switched to custom JSON module loader, it solved the problem for my angular 5 app:
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import * as translationEn from 'assets/i18n/en.json'; import * as translationRu from 'assets/i18n/ru.json'; const TRANSLATIONS = { en: translationEn, ru: translationRu, }; class JSONModuleLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return Observable.of(TRANSLATIONS[lang]); } } export function JSONModuleLoaderFactory() { return new JSONModuleLoader(); }typings.d.ts:
declare module '*.json' { const value: any; export default value; }
This solution worked like charme.... until i upgraded to Angular 9.
Does anyone used this solution with the new Angular Build Tool Ivy, which is the default in Angular 9.
Maybe i did something wrong...
Or does anyone have another solution for Angular 9 Universal and ngx-translate?
Plz help...
I used solution from this link on Angular 8, it prevents flickering but i dunno how it works on Angular 9 with Ivy
https://indepth.dev/implementing-multi-language-angular-applications-rendered-on-server/
I'm using angular 10 and I was also facing the flickering when the transition between browser and server would occur and since most of the issues with SSR and DOM rehydration are still open as of today, I decided to test the solution on https://indepth.dev/implementing-multi-language-angular-applications-rendered-on-server. Seems to have solved the issue.
Most helpful comment
I confirm that it works now like a charm, therefore I gonna summarized here the solution and close my issue.
PRECONDITION
This issue applies if you are using angular universal to build a multilanguage website and if you are facing a glitch while the app is rendered in the browser.
The source of the problem is that actually state transfer doesn't implement yet DOM rehydration (see https://github.com/angular/angular/issues/13446).
To solve this or to bypass this problem, the solution is to load and use ngx-translate on both server and browser side with custom loaders. The idea is to load the translations on the server side and to pass them to the browser side within the help of the
transfer-state.HOW TO
Cache your http request within the use of
TransferHttpCacheModulehttps://github.com/angular/universal/tree/master/modules/commonUse transfer-state to pass data from your server to your browser
https://angular.io/api/platform-browser/TransferStateCreate a loader for the server side
translate-server-loader.service.tsUse the server loader in your
app.server.module.tsCreate a loader for the browser side
translate-browser-loader.service.tsUse the server loader in your
app.browser.module.tsI've now deployed this solution in production, if you want to have a look, https://fluster.io
I really would like to thank you @ocombe for your support, I'm so happy, this is so neat without glitch, you are really awesome 馃憤