http-loader
http-loader copied to clipboard
Cached JSON file
When new bundle is deployed to production server and new translation keys have been added, last JSON is cached. It would be nice if, somehow, the loader could fetch the last version of the JSON file.
The way I provide the loader is like this:
export const createTranslateLoader = (http: Http) => {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
};
@NgModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [ Http ]
}
})
]
}
export class CoreModule { }
Is it maybe possible to provide a cachebusting query parameter as suffix? Something like:
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, '/assets/i18n/', '.json?cacheBuster='
+ environment.cacheBusterHash);
}
Then you only have to think about providing a caching hash on every production build. But sure, it would be nice if it is provided by the TranslateHttpLoader.
Yes, would be great to have. We are experiencing the same thing.
Yes please, another vote for this. Have just run into the same issue.
We've gotten around this by having a separate pre-build gulp task that takes all the generated translation files and prepends them with a unique guid/hash. Is a hacky solution that we're not crazy about.
If you are using webpack/angular-cli you can solve the problem by writing your own TranslateLoader
.
// webpack-translate-loader.ts
import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
export class WebpackTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return Observable.fromPromise(System.import(`../i18n/${lang}.json`));
}
}
Cause System
will not be available you need to add it to your custom typings.d.ts
declare var System: System;
interface System {
import(request: string): Promise<any>;
}
Now we can import everything in our app module
@NgModule({
bootstrap: [AppComponent],
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: WebpackTranslateLoader
}
})
]
})
export class AppModule { }
When you are using angular-cli
you can then build your cache busted files via ng build --prod
.
@mlegenhausen thanks for your reply! We do something similar, but are referencing our already created translation files created with a unique hash (from our pre gulp build task):
export function createTranslateLoader(http: HttpClient, configService: ConfigService) {
return new TranslateHttpLoader(http, '/assets/translations/', `.${configService.config.translationsHash || ''}.json`);
}
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient, ConfigService]
}
})
What exactly do you mean about build your cache busted files via ng build?
Normally webpack is able to create this unique identifiers for you. This does not work when you use for example the copy plugin from webpack. Which can lead to caching problems when you use high ttls for your static content. I also prefer the pure webpack way so I don't have to use a build tool for my build tool :wink:
I created http interceptor to set headers {'Cache-control': 'no-cache, must-revalidate'}. Add it to providers in module and it works fine.
Similar to @stetro solution above, I use a date timestamp for my caching hash. Saves me from having to manually set a cache hash for every build.
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, '/assets/i18n/', '.json?cb=' + new Date().getTime());
}
We are using https://github.com/manfredsteyer/ngx-build-plus and a similar approach to @errolgr solution. We want to update the timestamp only during a new deployment.
To build on @karlhaas's comment, here's something I've come up with, which allows for cache-busting on a per-build basis, rather than @errolgr's solution - which will force the client to retrieve the JSON files on a per-request basis of your project.
-
Find a way to expand on the webpack configuration used by Angular. @karlhaas points to ngx-build-plus, but I was already using @angular-builders/custom-webpack.
-
Use the
DefinePlugin
in-built webpack plugin, to define a version - in my case just the unix timestamp. Add it as aplugin
to your webpack config:
const webpack = require('webpack');
// Using @angular-builder's custom-webpack
module.exports = {
plugins: [
new webpack.DefinePlugin({
VERSION: (+new Date()).toString(),
}),
],
};
- Now you'll have access to this
VERSION
constant within your project files. You'll just need to usedeclare
to make TypeScript happy. Here's how I've used it
declare const VERSION: string;
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(
http, './assets/i18n/', `.json?version=${VERSION}`,
);
}
Now with angular 8 and esnext module loading this will also work. --prod build would add hashes to file names.
import { TranslateLoader } from '@ngx-translate/core';
import { Observable, from } from 'rxjs';
export class LazyTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return from(import(`../i18n/${lang}.json`));
}
}
in tsconfig.json you also need
...
"module": "esnext",
...
Now with angular 8 and esnext module loading this will also work. --prod build would add hashes to file names.
import { TranslateLoader } from '@ngx-translate/core'; import { Observable, from } from 'rxjs'; export class LazyTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return from(import(`../i18n/${lang}.json`)); } }
in tsconfig.json you also need
... "module": "esnext", ...
Hi @vixriihi ,
I've tried your solution but the i18n json files are not copied in the dist folder.
Do I need to add something in the angular.json
file?
Is correct to add the loader like the code below?
TranslateModule.forRoot({
defaultLanguage: 'en',
loader: {
provide: TranslateLoader,
useClass: LazyTranslateLoader,
}
})
Tried @vixriihi 's approach. But for some reason it is not working. For the record, I am building for Angular Universal. Would that make any change?
Is there a way to clear the cache with a function on logout? We have an application with multiple locations but different translations on each location. For example location A may have different translations for the translation "de" than location B. How do I clear the cache on logout or something similar such that when you swap locations the translations will be reloaded?
For someone looking to have their translation files deployed on prod with a different hash, I solved it similar to @errolgr. The trick is to set the environment variable with the time stamp like
export const environment ={
hash: "${new Date().toISOString().replace(/\.|:|-/g,'')}"
}
The regex above gets rid of dot(s), colon and hyphen as a result of the date object. The translation loader looks like follows
export function HttpLoaderFactory(http: HttpClient) {
return isDevMode() ? new TranslateHttpLoader(http) : new TranslateHttpLoader(http, '/assets/i18n/', '.json?cb=' + environment.hash);
}
If you are using webpack/angular-cli you can solve the problem by writing your own
TranslateLoader
.// webpack-translate-loader.ts import { TranslateLoader } from '@ngx-translate/core'; import { Observable } from 'rxjs/Observable'; export class WebpackTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return Observable.fromPromise(System.import(`../i18n/${lang}.json`)); } }
Cause
System
will not be available you need to add it to your customtypings.d.ts
declare var System: System; interface System { import(request: string): Promise<any>; }
Now we can import everything in our app module
@NgModule({ bootstrap: [AppComponent], imports: [ TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: WebpackTranslateLoader } }) ] }) export class AppModule { }
When you are using
angular-cli
you can then build your cache busted files viang build --prod
.
Thank you!!! In my case on Angular 9 (rxjs 6.5), I use 'from' instead of 'fromPromise'. Hope it would help sb.
// webpack-translate-loader.ts
import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
import { from } from 'rxjs';
export class WebpackTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return from(import(`../../../assets/i18n/${lang}.json`));
}
}
Now with angular 8 and esnext module loading this will also work. --prod build would add hashes to file names.
import { TranslateLoader } from '@ngx-translate/core'; import { Observable, from } from 'rxjs'; export class LazyTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return from(import(`../i18n/${lang}.json`)); } }
in tsconfig.json you also need
... "module": "esnext", ...
This approach doesn't work for me.
First, translation files aren't loaded due to ERROR Error: Uncaught (in promise): Error: Cannot find module 'assets/i18n/en-GB.json'
. Tested with multiple relative paths (src/assets
, assets
, /assets
, ../assets
, ../../assets
etc.) and an absolute one (https://localhost:4200/assets/i18n/en-GB.json
). File opens in a separate tab by tested path, but import()
can't find it.
Second, Warning: Critical dependency: the request of a dependency is an expression
.