Permalink
Browse files

Add `getObject` to `TranslateHelper`

  • Loading branch information...
baflo committed Jan 31, 2019
1 parent 02cbde2 commit f8306c81ef53a89306017ac815c7f781dd629e57
@@ -238,4 +238,68 @@ describe("TranslateHelper", function() {
});
});
});

describe("getObject", () => {
describe("with single string", () => {
it("returns single translated string", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.withSingleString")).toEqual("only alternative");
});

it("interpolates variables", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.withPlaceholder", { number: 4 })).toEqual("alternative 4");
});

it("resolve templates", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.withTemplate", { number: 4 })).toEqual(["alternative 1", "alternative 2"]);
});
});

describe("with array", function() {
it("returns array with all translations", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.withArray")).toEqual(["alternative 1", "alternative 2"]);
});
});

describe("nestedWithArrays", function() {
it("returns array with all translations", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.nestedWithArrays")).toEqual({
first: ["alternative 1", "alternative 2"],
second: ["alternative 3", "alternative 4"],
});
});
});

describe("nestedWithTemplates", function() {
it("returns object tree with all translated alternatives", async function() {
expect(await this.translateHelper.getObject("getObjectState.relativeIntent.nestedWithTemplates", { version: 2 })).toEqual({
a: {
meta: {
version: "2",
},
oneAndTwo: ["alternative 1", "alternative 2"],
threeAndFour: ["alternative 3", "alternative 4"],
},
});
});
});

describe("relative resolution", function() {
beforeEach(async function() {
this.context.intent = "relativeIntent";
this.context.state = "getObjectState";
});

it("behaves as above but with relative key", async function() {
expect(await this.translateHelper.getObject(".nestedWithTemplates", { version: 2 })).toEqual({
a: {
meta: {
version: "2",
},
oneAndTwo: ["alternative 1", "alternative 2"],
threeAndFour: ["alternative 3", "alternative 4"],
},
});
});
});
});
});
@@ -112,5 +112,38 @@
"alternative 1",
"alternative {2|3}"
]
},
"getObjectState": {
"relativeIntent": {
"withSingleString": "only alternative",
"withPlaceholder": "alternative {{number}}",
"withTemplate": "alternative {1|2}",
"withArray": [
"alternative 1",
"alternative 2"
],
"nestedWithArrays": {
"first": [
"alternative 1",
"alternative 2"
],
"second": [
"alternative 3",
"alternative 4"
]
},
"nestedWithTemplates": {
"a": {
"meta": {
"version": "{{version}}"
},
"oneAndTwo": "alternative {1|2}",
"threeAndFour": [
"alternative 3",
"alternative 4"
]
}
}
}
}
}
@@ -39,6 +39,18 @@ export interface TranslateHelper extends ShortT {
* @param locals If given: variables to use in response
*/
getAllAlternatives(locals?: { [name: string]: string | number | object }): Promise<string[]>;

/**
* Works the same as the regular t(), but returns any structure below the given key. The exact structure is returned, regardless
* if the key points to a string, array or object, except for combinational template strings which are resolved to arrays.
*
* @param key String of the key to look for. If you pass a relative key (beginning with '.'),
* this method will apply several conventions, first looking for a translation for "currentState.currentIntent.KEY.platform.device".
* If you pass an absolute key (without "." at beginning), this method will look at given absolute key.
* @param locals Variables to use in reponse
* @return nested structure with translations
*/
getObject(key?: string, locals?: { [name: string]: string | number | object }): Promise<string | string[] | object>;
}

export interface InterpolationResolver {
@@ -108,6 +108,41 @@ export class TranslateHelper implements TranslateHelperInterface {
return translation.split(arraySplitter);
}

public async getObject(key?: string, locals: { [name: string]: string | number | object } = {}): Promise<string | string[] | object> {
function splitStrings(argObj: any) {
// Parse JSON objects and arrays to JavaScript literals and keep all other primitives
const obj = typeof argObj === "string" && /^[\[|\{]/.test(argObj) ? JSON.parse(argObj) : argObj;

if (typeof obj === "string") {
// Resolve `arraySplitter` for combinations resulting from `{a|b}` templates
return obj.indexOf(arraySplitter) !== -1 ? obj.split(arraySplitter) : obj;
}

for (const k in obj) {
if (obj.hasOwnProperty(k)) {
obj[k] = splitStrings(obj[k]);
}
}

return obj;
}

// Set internal assistantjs option for array-returns-sample.plugin
locals[optionsObjectName] = { [optionEnablingArrayReturn]: true };

// Get regular translation string. Multiple translations are concatenated by arraySplitter per default...
const translation = await this.t(key as any, {
...locals,
/* Maintain object structure below requested key */
returnObjects: true,
/* Don't used `arraySplitter` to join keys and maintain structure */
joinArrays: false,
});

// ... so we have to split to return in array format
return splitStrings(translation);
}

/**
* Finds first existing locale or throws exception if none of the lookups exist.
* i18n.exists() won't work here: it returns true for keys returning an object, even if returnObjectTrees is false. t() then returns undefined.
@@ -116,6 +151,12 @@ export class TranslateHelper implements TranslateHelperInterface {
for (const lookup of lookups) {
if (this.i18n.exists(lookup, options)) {
const translation = this.i18n.t(lookup, options);

if (typeof translation === "object" && (options as any).returnObjects) {
this.logger.debug("I18N: choosing key: " + lookup);
return JSON.stringify(translation);
}

if (typeof translation === "string") {
this.logger.debug("I18N: choosing key: " + lookup);
return translation;

0 comments on commit f8306c8

Please sign in to comment.