Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions angular2-translator/TranslateLoaderJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Inject, Injectable} from "@angular/core";
import {Http} from "@angular/http";

export class TranslateLoaderJsonConfig {
public path: string = "i18n/";
public path: string = "i18n/";
public extension: string = ".json";

// @todo maybe we will change it to a destructed parameter like we did for TranslateConfig
Expand Down Expand Up @@ -36,19 +36,8 @@ export class TranslateLoaderJson extends TranslateLoader {
.subscribe(
(response) => {
if (response.status === 200) {
let translations = response.json();
let key;
for (key in translations) {
if (Array.isArray(translations[key])) {

translations[key] = translations[key]
.filter((v) => typeof v === "string")
.join("");

} else if (typeof translations[key] !== "string") {
delete translations[key];
}
}
let translations = {};
this.flattenTranslations(translations, response.json());
resolve(translations);
} else {
reject("StatusCode: " + response.status + "");
Expand All @@ -60,4 +49,16 @@ export class TranslateLoaderJson extends TranslateLoader {
);
});
}

private flattenTranslations(translations: any, data: any, prefix: string = "") {
for (let key in data) {
if (Array.isArray(data[key])) {
translations[prefix + key] = data[key].filter(v => typeof v === "string").join("");
} else if (typeof data[key] === "object") {
this.flattenTranslations(translations, data[key], prefix + key + ".");
} else if (typeof data[key] === "string") {
translations[prefix + key] = data[key];
}
}
}
}
37 changes: 37 additions & 0 deletions docs/TranslateLoaderJson.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,43 @@ To keep order in your translation file your can use arrays for translations. Exa
}
```

## Nested translation tables

For more structure in your translation file we allow objects. Please note that they are merged to one dimension.

```json
{
"app": {
"loginText": "Please login before continuing!",
"componentA": {
"TEXT": "something else"
}
}
}
```

The translation table becomes:

```json
{
"app.loginText": "Please login before continuing!",
"app.componentA.TEXT": "something else"
}
```

So you can access them with `translate('app.loginText')`. You need to refer to translations with full key too:

```json
{
"app": {
"A": "This gets \"something else\": [[ TEXT ]]",
"B": "This gets \"something\" [[ app.TEXT ]]",
"TEXT": "something"
},
"TEXT": "something else"
}
```

## TranslateLoaderJsonConfig

To configure TranslateLoaderJson you can create your own TranslateLoaderJsonConfig and provide it.
Expand Down
168 changes: 152 additions & 16 deletions tests/TranslateLoaderJson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ describe("TranslateLoaderJson", function () {
describe("constructor", function () {
it("requires a TranslateLoaderJsonConfig", function () {
TestBed.configureTestingModule({
imports: [ HttpModule ],
providers: [ TranslateLoaderJson ],
imports: [HttpModule],
providers: [TranslateLoaderJson],
});

let action = function () {
Expand All @@ -66,16 +66,16 @@ describe("TranslateLoaderJson", function () {

beforeEach(function () {
TestBed.configureTestingModule({
imports: [ HttpModule ],
imports: [HttpModule],
providers: [
{ provide: XHRBackend, useClass: MockBackend },
{ provide: TranslateLoaderJsonConfig, useValue: new TranslateLoaderJsonConfig() },
{provide: XHRBackend, useClass: MockBackend},
{provide: TranslateLoaderJsonConfig, useValue: new TranslateLoaderJsonConfig()},
TranslateLoaderJson,
],
});

backend = TestBed.get(XHRBackend);
loader = TestBed.get(TranslateLoaderJson);
backend = TestBed.get(XHRBackend);
loader = TestBed.get(TranslateLoaderJson);
backend.connections.subscribe((c: MockConnection) => connection = c);

PromiseMatcher.install();
Expand Down Expand Up @@ -105,29 +105,29 @@ describe("TranslateLoaderJson", function () {
expect(request.method).toBe(RequestMethod.Get);
});

it("resolves when connection responds", function() {
it("resolves when connection responds", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify({ TEXT: "This is a text" }),
body: JSON.stringify({TEXT: "This is a text"}),
status: 200,
})));

expect(promise).toBeResolved();
});

it("transforms result to object", function() {
it("transforms result to object", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify({ TEXT: "This is a text" }),
body: JSON.stringify({TEXT: "This is a text"}),
status: 200,
})));

expect(promise).toBeResolvedWith({ TEXT: "This is a text" });
expect(promise).toBeResolvedWith({TEXT: "This is a text"});
});

it("rejectes when connection fails", function() {
it("rejectes when connection fails", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
Expand All @@ -138,15 +138,15 @@ describe("TranslateLoaderJson", function () {
expect(promise).toBeRejectedWith("StatusCode: 500");
});

it("rejects when connection throws", function() {
it("rejects when connection throws", function () {
let promise = loader.load("en");

connection.mockError(new Error("Some reason"));

expect(promise).toBeRejectedWith("Some reason");
});

it("combines arrays to a string", function() {
it("combines arrays to a string", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
Expand All @@ -165,7 +165,143 @@ describe("TranslateLoaderJson", function () {
});
});

it("filters non string values", function() {
it("allows nested objects", function () {
let promise = loader.load("en");
let nestedObj: any = {
TEXT: {
NESTED: "This is a text",
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({"TEXT.NESTED": "This is a text"});
});

it("allows multiple nested objects", function () {
let promise = loader.load("en");
let nestedObj: any = {
TEXT: {
NESTED: "This is a text",
SECONDNEST: {
TEXT: "Second text",
},
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({"TEXT.NESTED": "This is a text", "TEXT.SECONDNEST.TEXT": "Second text"});
});

it("combines arrays to a string while returning nested objects", function () {
let promise = loader.load("en");
let nestedObj: any = {
COOKIE_INFORMATION: [
"We are using cookies to adjust our website to the needs of our customers. ",
"By using our websites you agree to store cookies on your computer, tablet or smartphone.",
],
TEXT: {
NESTED: "This is a text",
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({
COOKIE_INFORMATION: "We are using cookies to adjust our website to " +
"the needs of our customers. By using our websites you agree to store cookies on your computer, " +
"tablet or smartphone.", "TEXT.NESTED": "This is a text",
});
});

it("allows nested objects with lower case keys and with camel case", function () {
let promise = loader.load("en");
let nestedObj: any = {
text: {
nestedText: "This is a text",
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({"text.nestedText": "This is a text"});
});

it("filters non string values within nested object", function () {
let promise = loader.load("en");
let nestedObj: any = {
TEXT: {
ANSWER: 42,
NESTED: "This is a text",
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({"TEXT.NESTED": "This is a text"});
});

it("combines arrays to a string while beeing in nested objects", function () {
let promise = loader.load("en");
let nestedObj: any = {
TEXT: {
COOKIE_INFORMATION: [
"We are using cookies to adjust our website to the needs of our customers. ",
"By using our websites you agree to store cookies on your computer, tablet or smartphone.",
],
},
};

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(nestedObj),
status: 200,
})));

expect(promise).toBeResolvedWith({
"TEXT.COOKIE_INFORMATION": "We are using cookies to adjust our website " +
"to the needs of our customers. By using our websites you agree to store cookies on your " +
"computer, tablet or smartphone.",
});
});

it("merges translations to one dimension", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify({
app: {
componentA: {
TEXT: "something else",
},
loginText: "Please login before continuing!",
},
}),
status: 200,
})));

expect(promise).toBeResolvedWith({
"app.componentA.TEXT": "something else",
"app.loginText": "Please login before continuing!",
});
});

it("filters non string values", function () {
let promise = loader.load("en");

connection.mockRespond(new Response(new ResponseOptions({
Expand Down
14 changes: 14 additions & 0 deletions tests/TranslateService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,20 @@ describe("TranslateService", function () {
);
}));

it("allows dots in key", fakeAsync(function() {
translate.waitForTranslation();
loaderPromiseResolve({
HELLO: "Hello [[ app.WORLD ]]!",
"app.WORLD": "World",
});
JasminePromise.flush();
spyOn(translate.logHandler, "error").and.callFake(() => {});

let translation = translate.instant("HELLO");

expect(translation).toBe("Hello World!");
}));

it("key is finish after space character", fakeAsync(function() {
translate.waitForTranslation();
loaderPromiseResolve({
Expand Down