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

Loading Multiple file from a directory. #199

Open
santhanam87 opened this issue Aug 16, 2016 · 39 comments
Open

Loading Multiple file from a directory. #199

santhanam87 opened this issue Aug 16, 2016 · 39 comments

Comments

@santhanam87
Copy link

My application is big and I can't keep all the keys related to one language in a file, that would be so huge to load, Is there is any way to handle this case ?

Thanks,
Santhanam E

@ocombe
Copy link
Member

ocombe commented Aug 17, 2016

The method setTranslation has a 3rd parameter "shouldMerge: boolean" that you can use if you want to append translations instead of replacing them. You will have to use your own loader for that probably, but it's possible :)

@nabeelbukhari
Copy link

Are there any plans of providing built-in support for partial loading?

@nabeelbukhari
Copy link

nabeelbukhari commented Sep 24, 2016

I have implemented one for loading multiple files.

`
/**/
import {TranslateLoader} from 'ng2-translate/ng2-translate';
import {Http, Response} from "@angular/http";
import {Observable} from "rxjs/Observable";

import '../../rxjs-extensions'

export class TranslateParitalLoader implements TranslateLoader {
constructor(private http: Http, private prefix: Array = ["i18n"], private suffix: string = ".json") {
}

/**
 * Gets the translations from the server
 * @param lang
 * @returns {any}
 */
getObservableForHttp(value, combinedObject, lang: string) {
    return Observable.create(observer => {
        this.http.get(`${value}/${lang}${this.suffix}`)
        .subscribe((res) => {
            let responseObj = res.json();
            Object.keys(responseObj).forEach(key=>{
                combinedObject[key] = responseObj[key];
            });
            console.log(combinedObject);
            observer.next(combinedObject);
            //call complete if you want to close this stream (like a promise)
            observer.complete();
        });
    });
}

public getTranslation(lang: string): Observable<any> {
    var combinedObject = new Object();
    var oldObsevers;
    var newObserver;
    this.prefix.forEach((value) =>{
        newObserver = this.getObservableForHttp(value, combinedObject, lang);
        if (oldObsevers == null) {
            oldObsevers = newObserver;
        }
        else {
            oldObsevers = oldObsevers.merge(newObserver);
        }
    });
    return oldObsevers;
}

}`

@ganesh35
Copy link

ganesh35 commented Oct 3, 2016

I too run in to this problem. Highly appreciated if the solution is in-built.
Ganesh

@deepu105
Copy link
Contributor

@ocombe we are using angular-i18n for https://jhipster.github.io and now we are migrating to provide ng2 support. we are trying to use ng2-translate here but we are having trouble as we have a lot of partial files for each language and we used the partialLoader from angular-i18n.
JHipster should bring you a lot users and downloads and it would be highly appreciated if the feature can be provided out of the box
The PR for this is jhipster/generator-jhipster#4304

@anphu7492
Copy link

+1 for partial loading support

@deepu105
Copy link
Contributor

deepu105 commented Dec 9, 2016 via email

@ocombe
Copy link
Member

ocombe commented Dec 10, 2016

Is the loader somewhere public, so that I can add a link to it ?

@deepu105
Copy link
Contributor

@deepu105
Copy link
Contributor

But it would be even better if its supported out of the box as it would be the most practical use case in real systems

@ocombe
Copy link
Member

ocombe commented Dec 21, 2016

I'll add this with the new modular system for the next major version, the lib will use scoped modules and you will have multiple loaders to choose from in order to compose your perfect translate library :)

@Matmo10
Copy link

Matmo10 commented Feb 10, 2017

Hi @ocombe, so this next major version will allow you to split up your translations by modules? So lazy loaded modules can also have their translations files lazily loaded? Is that correct?

Any rough ETA's on that?

@ocombe
Copy link
Member

ocombe commented Feb 10, 2017

Yes that's correct, the beta 1 of the new version (6.0.0-beta.1) is already available

@ocombe ocombe added this to the 6.0.0 milestone Feb 10, 2017
@Richie765
Copy link

Richie765 commented Feb 18, 2017

Loading separate language files for modules didn't work for me yet. As a workaround I'm using the following Gulp:

var gulp = require('gulp');
var merge = require('gulp-merge-json');

var fs = require('fs');
var path = require('path');

var languages = 'en,nl,de,es,pt'.split(',');
var i18n_source = 'resources/i18n';
var i18n_dest = 'src/assets/i18n';

// Currently unused but could be handy
function getDirs(dir) {
  return fs.readdirSync(dir).filter(function(file) {
    return fs.statSync(path.join(dir, file)).isDirectory();
  });
}
// Merge multiple i18n json files together

gulp.task('i18n', function() {
  return languages.map(function(lang) {
    return gulp.src(`${ i18n_source }/*/${ lang }.json`)
      .pipe(merge({
        fileName: `${ lang }.json`
      }))
      .pipe(gulp.dest(i18n_dest));
  });
});

It loads files from e.g. resources/*/en.json, merges them and spits them out to src/assets/i18n/en.json

@elendil-software
Copy link

Hi

I'm a bit lost. Does the 6.0.0 version support multiple file loading ?

Regards

Julien

@Matmo10
Copy link

Matmo10 commented Mar 6, 2017

Hey @ocombe, I know you're probably super busy these days with your new work on the Angular team, but I was wondering if you could briefly explain how to use the partial loader for translations that only need to be loaded as the modules they belong to are lazily loaded.

When you said you will have multiple loaders to choose from in order to compose your perfect translate library earlier in this thread - are those loaders already available in the master branch? I was digging around the source code and didn't seem to find any. If they exist, could you point them out so we can at least guesstimate how they should be used? Thanks :)

@ocombe
Copy link
Member

ocombe commented Mar 7, 2017

Hey, yes super busy with my new work in the core team (I expected to have more free time once I was freelance, but I'm actually working more).

To use a partial loader, someone will have to write one, it should be easy to do but I don't have much time to work on this :-/
If someone wants to start working on it, I could help

@k11k2
Copy link

k11k2 commented May 26, 2017

Is there any doc regarding how to use multiple loaders ?

@marko033
Copy link

Someone started working on partial loader?

@ghost
Copy link

ghost commented Jun 13, 2017

@ocombe What will they come out with next?

@rubenns
Copy link

rubenns commented Jun 16, 2017

+1 for partial loading support

@Selupsis
Copy link

Selupsis commented Jul 6, 2017

Thank you @nabeelbukhari for the provided code snippet, loading translations from several folders works for me now.

However, when I use the custom loader, the translate-pipe does not work anymore - it always just returns the key that should be translated. Translations are loaded successfully, and using e.g. the directive translates values correctly. Am I missing something?

@vincentfierant
Copy link

vincentfierant commented Jul 28, 2017

@Selupsis the reason the pipe is not working in @nabeelbukhari 's example is because it is calling observer.next() / observer.complete() too soon. It should call it after ALL paths have been loaded. I fixed this by implementing a counter and only calling .next/.complete after all paths are loaded.

I'm sure it can be written a lot cleaner or smarter, so happy to see any improvements!
https://gist.github.com/vincentfierant/babfff11a152d4ca8d432b5c938ae2e0

@nirzamir
Copy link

nirzamir commented Aug 3, 2017

Hi,

Using Observable.reduce worked for me for merging two translation files (for the sake of experiment I just concatenated '2' to the file name, but obviously you can refactor it to a loop over multiple prefixes). It works with the pipe - translations from both files are working fine.

class CustomLoader implements TranslateLoader {
constructor(private http: HttpClient, private prefix: string = "/assets/i18n/", private suffix: string = ".json") {}

    public getTranslation(lang: string): any {
      const $firstFile = this.http.get(`${this.prefix}${lang}${this.suffix}`);
      const $secondFile = this.http.get(`${this.prefix}${lang}2${this.suffix}`);
      const reducer = (translations, val) => { return Object.assign(translations, val) };
      return Observable.merge($firstFile, $secondFile)
              .reduce(reducer, {});
    }
}

@Tuizi
Copy link

Tuizi commented Sep 23, 2017

I wrote a article about how to have 1 json file per lazy loaded module without having to write a new Custom Loader etc... it's quiet simple, only the documentation is not clear in fact:
https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f

@BorisWechselberger
Copy link

BorisWechselberger commented Oct 24, 2017

import {HttpClient} from '@angular/common/http';
import {TranslateLoader} from '@ngx-translate/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';

export function translateLoader(http: HttpClient) {
  
  return new MultiTranslateHttpLoader(http, [
    {prefix: './assets/i18n/', suffix: '.json'},
    {prefix: './assets/i18n/countries-', suffix: '.json'}
  ]);
}

export class MultiTranslateHttpLoader implements TranslateLoader {
  
  constructor(private http: HttpClient,
              public resources: { prefix: string, suffix: string }[] = [{
                prefix: '/assets/i18n/',
                suffix: '.json'
              }]) {}

  /**
   * Gets the translations from the server
   * @param lang
   * @returns {any}
   */
  public getTranslation(lang: string): any {

    return Observable.forkJoin(this.resources.map(config => {
      return this.http.get(`${config.prefix}${lang}${config.suffix}`);
    })).map(response => {
      return response.reduce((a, b) => {
        return Object.assign(a, b);
      });
    });
  }
}

https://gist.github.com/BorisWechselberger/08e2424e1267ed27f9b4a046cc3357c8

@Willis0826
Copy link

@BorisWechselberger I did tried the code you provided above, it works for me!!!

Here is a problem I have faced, I guessed this problem is caused by the different package version:

  1. We should ensure the getTranslation() will return Object, not JSON String.

To solve this problem, I changed the getTranslation() like this :

public getTranslation(lang: string): any {
    return Observable.forkJoin(this.resources.map(config => {
        return this.http.get(`${config.prefix}${lang}${config.suffix}`);
    })).map(response => {
        return response.reduce((a:any , b:any) => {
            a._body = JSON.parse(a._body); //parse JSON String to Javascript Object
            b._body = JSON.parse(b._body); //parse JSON String to Javascript Object
            let obj:any = Object.assign(a._body, b._body);
            return obj;
        });
    });
}

Here to share!! Feel free to feedback.

@AlbertoFCasarrubias
Copy link

@Willis0826 , how do you implement it ? in app.module , app.component ?

@ghost
Copy link

ghost commented Dec 12, 2017 via email

@Willis0826
Copy link

Willis0826 commented Dec 18, 2017

@yaotzin68 Sorry for my late reply ! Yes, I implement the MultiTranslateHttpLoader class in app.module.

@denniske
Copy link
Contributor

I have created a github repository for a ngx translate http loader that can load multiple translation files:
https://github.com/denniske/ngx-translate-multi-http-loader

Simple example: https://stackblitz.com/edit/ngx-translate-multi-http-loader-sample

@kourosko
Copy link

I edited @Richie765 's comment so it can autodetect languages and I added a watch so it can detect changes in language files.
code:

var gulp = require('gulp');
var merge = require('gulp-merge-json');
var i18n_source = 'src/resources/i18n';
var i18n_dest = 'src/assets/i18n';
var glob = require('glob');

// Merge multiple i18n json files together
function fixi18n(done) {
  const langs = [
    ...new Set(
      glob
        .sync(`${i18n_source}/**/*.json`)
        .map(x => x.match(/[ \w-]+?(?=\.)/gm)[0])
    )
  ];
  console.log('Fixing languages: ', langs);
  return langs.map(function(lang) {
    return gulp
      .src(`${i18n_source}/**/${lang}.json`)
      .pipe(
        merge({
          fileName: `${lang}.json`
        })
      )
      .pipe(gulp.dest(i18n_dest, { overwrite: true }))
      .on('end', () => done());
  });
}
gulp.task('i18n', fixi18n);
function watchAppJs(done) {
  return gulp
    .watch(`${i18n_source}/**/*.*`, gulp.series(fixi18n))
    .on('end', () => done());
}
gulp.task('default', gulp.series(fixi18n, watchAppJs));

Instead of ng serve i run with concurrently eg of package.json

 "scripts": {
    ...
    "prebuild": "gulp i18n",
    "build": "ng build",
    "serve": "concurrently --raw --kill-others --kill-others-on-fail  \\"ng serve\\" \\"gulp\\" "
..
}

@Ruud-cb
Copy link

Ruud-cb commented Apr 25, 2019

Hi,

@BorisWechselberger 's solution works, but so far as I can see, all the files will still be loaded on initial request? I have tried to use forChild() in a lazy loaded method and then provide the custom loader, but that does not seem to work, I have to provide the loader at forRoot()

So it is required to do this in app.module.ts

    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: translateLoader, //<-- Boris's loader
        deps: [HttpClient]
      },
      isolate: false
    }),

I cannot use the default loader in app.module.ts, and once the user visits a lazy-loaded module that does need those translations, include those extra files, or can I?

lazy.module.ts (whatever sub-module in a web application that is loaded via routing)

    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: translateLoader, //<-- Boris's loader
        deps: [HttpClient]
      },
      isolate: false
    }),

I have also tried to follow the default documentation, using useClass but that didn't help either:
I also modified the MultiTranslateHttpLoader constructor to include the extra file but getTranslation is never called anyway

    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useClass: MultiTranslateHttpLoader,
        deps: [HttpClient]
      },
      isolate: false
    }),

@ocombe is this a bug? It does hit the constructor of MultiTranslateHttpLoader but getTranslation is not called. Looking forward to what ngx-translate v6 will bring.

How can I achieve this? Because as it seems now it would still need to load each file on initial request, I am not sure what the benefit of that is?

And for newcomers, you have to modify getTranslation with newer version of rxjs (from 6.x I believe)

   import { forkJoin } from 'rxjs';
  import { map } from 'rxjs/operators';

  public getTranslation(lang: string): any {

    return forkJoin(this.resources.map(config => {
      return this.http.get(`${config.prefix}${lang}${config.suffix}`);
    })).pipe(map(response => {
      return response.reduce((a, b) => {
        return Object.assign(a, b);
      });
    }));
  }

@JBorgia
Copy link

JBorgia commented Jun 10, 2019

Is there a way to have a file for each module in the Angular application and have the JSON structured so each translated text piece has all the language translations at the same point rather than in separate files? The risk of missing a piece would be greatly reduced. For example, something like this:

{ 'xm.sidebar.links.1': { title: 'Sidebar Link 1', desc: 'Top level navigation link 1', en: 'Home', fr: 'Maison', de: 'Zuhause' } }

As Angular apps are built (and split) around modularity, being able to structure and access translations in this manner would seem to make it easier to maintain. Additionally, it'd allow for it to be easier for a translation file to be a somewhat flat JSON so it would be simple to use the same text in multiple locations to keep changes uniform where required (if a reused term 'frequency' needed to be replaced with 'freq/dBm' it could be managed with a change in just one place).

@cgatian
Copy link

cgatian commented Oct 1, 2019

My company, Hyland Software, has created a angular cli builder that will aggregate language files from libraries and app to create one master translation file per each language. Is this something the community would be interested in if we open sourced it?

@ocombe
Copy link
Member

ocombe commented Oct 1, 2019

@cgatian I was planning on making something similar for my future library Locl, I'd like to take a look at it at least, if you don't decide to distribute it

@cgatian
Copy link

cgatian commented Oct 1, 2019

@ocombe perhaps we can leave it generic enough to be used by multiple libraries? If you wouldnt mind hit me up on Twitter @cgatian

@ocombe
Copy link
Member

ocombe commented Oct 4, 2019

@cgatian sorry your DMs are closed on Twitter, you'll have to send me a message @ocombe 🙂

@zavakare
Copy link

zavakare commented May 21, 2020

Anyone have an idea why I get this error?
image
I am trying BorisWechselberger implementation

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

No branches or pull requests