Skip to content
This repository has been archived by the owner on Nov 8, 2021. It is now read-only.

Cached JSON file #25

Open
penniath opened this issue Sep 6, 2017 · 18 comments
Open

Cached JSON file #25

penniath opened this issue Sep 6, 2017 · 18 comments

Comments

@penniath
Copy link

penniath commented Sep 6, 2017

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 { }
@stetro
Copy link

stetro commented Sep 27, 2017

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.

@bjornharvold
Copy link

Yes, would be great to have. We are experiencing the same thing.

@mathew-pigram
Copy link

Yes please, another vote for this. Have just run into the same issue.

@boldwade
Copy link

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.

@mlegenhausen
Copy link

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.

@boldwade
Copy link

boldwade commented Oct 26, 2017

@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?

@mlegenhausen
Copy link

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 😉

seveves added a commit to seveves/http-loader that referenced this issue Jan 30, 2018
In issue ngx-translate#25 @mlegenhausen pointed out a solution that shows a very common use-case for a custom TranslateLoader implementation.
Maybe we can extend the readme to show this (and maybe other use-cases in the future)?

Kind regards :)
@dz-rep
Copy link

dz-rep commented Feb 13, 2018

I created http interceptor to set headers {'Cache-control': 'no-cache, must-revalidate'}. Add it to providers in module and it works fine.

ocombe pushed a commit that referenced this issue Mar 16, 2018
* Howto write a HashTranslateLoader

In issue #25 @mlegenhausen pointed out a solution that shows a very common use-case for a custom TranslateLoader implementation.
Maybe we can extend the readme to show this (and maybe other use-cases in the future)?

Kind regards :)

* Updated to review
@errolgr
Copy link

errolgr commented Aug 16, 2018

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());
}

@karlhaas
Copy link

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.

@evenicoulddoit
Copy link

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.

  1. 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.

  2. Use the DefinePlugin in-built webpack plugin, to define a version - in my case just the unix timestamp. Add it as a plugin to your webpack config:

const webpack = require('webpack');

// Using @angular-builder's custom-webpack
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      VERSION: (+new Date()).toString(),
    }),
  ],
};
  1. Now you'll have access to this VERSION constant within your project files. You'll just need to use declare 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}`,
  );
}

@vixriihi
Copy link

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",
...

@roffus
Copy link

roffus commented Jun 9, 2020

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,
      }
    })

@poleindraneel
Copy link

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?

@tsafadi
Copy link

tsafadi commented Jul 20, 2020

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?

@austi10
Copy link

austi10 commented Feb 5, 2021

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);
}

@jyzbamboo
Copy link

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.

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`));
  }
}

@maks-humeniuk
Copy link

maks-humeniuk commented Oct 22, 2021

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests