ngx-translate / core

The internationalization (i18n) library for Angular
MIT License
4.51k stars 576 forks source link

Loading Multiple file from a directory. #199

Open santhanam87 opened 8 years ago

santhanam87 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

nabeelbukhari commented 8 years ago

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 commented 8 years ago

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

deepu105 commented 8 years ago

@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 https://github.com/jhipster/generator-jhipster/pull/4304

anphu7492 commented 7 years ago

+1 for partial loading support

deepu105 commented 7 years ago

Btw we have implemented a custom loader in JHipster for this and works quite nice :)

Thanks & Regards, Deepu

On Fri, Dec 9, 2016 at 1:28 AM, Phu Pham notifications@github.com wrote:

+1 for partial loading support

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ocombe/ng2-translate/issues/199#issuecomment-265897773, or mute the thread https://github.com/notifications/unsubscribe-auth/ABDlFxl3vgPSV7QzxDoKjrMGTp_OkXjVks5rGKCtgaJpZM4JlqmZ .

ocombe commented 7 years ago

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

deepu105 commented 7 years ago

Yes its on the JHipster repository https://github.com/jhipster/generator-jhipster/blob/master/generators/client/templates/angular/src/main/webapp/app/shared/language/_translate-partial-loader.provider.ts

deepu105 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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

Richie765 commented 7 years ago

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 commented 7 years ago

Hi

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

Regards

Julien

Matmo10 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

Is there any doc regarding how to use multiple loaders ?

marko033 commented 7 years ago

Someone started working on partial loader?

ghost commented 7 years ago

@ocombe What will they come out with next?

rubenns commented 7 years ago

+1 for partial loading support

Selupsis commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago
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 commented 6 years ago

@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 commented 6 years ago

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

ghost commented 6 years ago

Nice work, I'll probably need this momentarily.

On Tue, Dec 12, 2017 at 3:03 PM, Alberto Fuentes notifications@github.com wrote:

@Willis0826 https://github.com/willis0826 , how do you implement it ? in app.module , app.component ?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ngx-translate/core/issues/199#issuecomment-351209778, or mute the thread https://github.com/notifications/unsubscribe-auth/AX5XbtINz_iFx5M8uFAzWtQiO_-WDVxLks5s_vgdgaJpZM4JlqmZ .

-- "I studied engineering"

CONFIDENTIALITY NOTICE: The contents of this email message and any attachments are intended solely for the addressee(s) and may contain confidential and/or privileged information and may be legally protected from disclosure. It is then shared with tech companies, bots, hackers, government agencies, and marketers. The security of this message is none, and it may be shared on Instagram at anytime. If you are OK with this, please respond. There isn't really any security or privacy anywhere. If you disagree you may want to go camping and talk to people face-to-face like in old times.

Willis0826 commented 6 years ago

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

denniske commented 6 years ago

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 commented 6 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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

zavakare commented 4 years ago

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