Skip to content

Commit a7804e7

Browse files
committed
Added translations
1 parent f02ae64 commit a7804e7

File tree

8 files changed

+134
-10
lines changed

8 files changed

+134
-10
lines changed

src/lib/common/object/merge.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* eslint-disable no-param-reassign */
2+
import isObject from "./is-object";
3+
4+
export default function merge(...objects) {
5+
return objects.reduce((target, source) => {
6+
Object.keys(source).forEach((key) => {
7+
if(Array.isArray(target[key]) && Array.isArray(source[key])) {
8+
target[key] = target[key].concat(...source[key]);
9+
} else if(isObject(target[key]) && isObject(source[key])) {
10+
target[key] = merge(target[key], source[key]);
11+
} else {
12+
target[key] = source[key];
13+
}
14+
});
15+
16+
return target;
17+
}, {});
18+
}

src/lib/hooks/use-i18n.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
import {useEffect, useReducer} from "react";
12
import useLoadedValue from "lib/hooks/use-loaded-value";
2-
import {i18nState} from "lib/recoil";
3+
import {i18nState, translationsQuery} from "lib/recoil";
34

45
export default function useI18n() {
5-
return useLoadedValue(i18nState);
6+
const i18n = useLoadedValue(i18nState);
7+
const translations = useLoadedValue(translationsQuery);
8+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
9+
10+
useEffect(() => {
11+
if(!translations) { return; }
12+
13+
Object.keys(translations).forEach((locale) => {
14+
i18n.addTranslations(locale.toLowerCase(), {data: translations[locale.toLowerCase()]});
15+
});
16+
17+
forceUpdate();
18+
}, [translations]);
19+
20+
return i18n;
621
}

src/lib/i18n-data/en-us.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@
154154
"benchmarks": {
155155
"current": "Current Benchmark",
156156
"search": "Search for Benchmark"
157+
},
158+
"reports": {
159+
"candidate": "Candidate Report",
160+
"employee": "Engage Employee Report",
161+
"manager": "Hiring Manager Report"
157162
}
158163
},
159164
"room_for_growth_and_change": "Room for Growth and Change",

src/lib/i18n.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dig from "./common/object/dig";
2+
import merge from "./common/object/merge";
23
import i18nData from "./i18n-data";
34

45
export default class I18n {
@@ -11,11 +12,8 @@ export default class I18n {
1112
const locale = _locale.toLowerCase();
1213
const currentData = this.data[locale] || {};
1314

14-
this.data[locale] = {
15-
...currentData,
16-
...data
17-
};
18-
this.supportedLocales[locale] = name;
15+
this.data[locale] = merge(currentData, data);
16+
if(name) { this.supportedLocales[locale] = name; }
1917
}
2018
copyTranslations(_originLocale, _targetLocale) {
2119
const originLocale = _originLocale.toLowerCase();

src/lib/recoil/organization.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable import/prefer-default-export */
21
import {selector} from "recoil";
32
import camelCase from "lib/common/string/camel-case";
43
import {
@@ -15,7 +14,7 @@ export const settingsQuery = selector({
1514
const cached = cache.get(cacheKey);
1615
if(cached) { return cached; }
1716

18-
const response = await http.get(`/organizations/settings`);
17+
const response = await http.get("/organizations/settings");
1918
if(!response) { return; }
2019
if(response.errors) {
2120
console.warn("test", response.errors); /* eslint-disable-line no-console */
@@ -26,9 +25,31 @@ export const settingsQuery = selector({
2625
(newObject, key) => ({...newObject, [camelCase(key)]: response[key]}),
2726
{}
2827
);
29-
cache.set(cacheKey, settings, {expiresIn: 60 * 24});
28+
cache.set(cacheKey, settings, {expiresIn: 60 * 60 * 3});
3029

3130
return settings;
3231
},
3332
key: "settings"
3433
});
34+
35+
export const translationsQuery = selector({
36+
get: async({get}) => {
37+
const http = get(httpState);
38+
const cache = get(cacheState);
39+
const cacheKey = get(safeCacheKeyState({id: http.authKey || "none", type: "translations"}));
40+
const cached = cache.get(cacheKey);
41+
if(cached) { return cached; }
42+
43+
const response = await http.get("/xavier/translations", {project: "widgets"});
44+
if(!response) { return; }
45+
if(response.errors) {
46+
console.warn("test", response.errors); /* eslint-disable-line no-console */
47+
return;
48+
}
49+
50+
cache.set(cacheKey, response, {expiresIn: 60 * 60 * 3});
51+
52+
return response;
53+
},
54+
key: "translations"
55+
});

test/lib/hooks/use-i18n.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {createRef} from "react";
2+
import useI18n from "lib/hooks/use-i18n";
3+
import ComponentHandler from "support/component-handler";
4+
import {mockTranslations} from "support/container/http";
5+
import useContainer from "support/hooks/use-container";
6+
7+
describe("useI18n", () => {
8+
let value;
9+
10+
function Component() {
11+
const i18n = useI18n();
12+
value.current = i18n;
13+
14+
return null;
15+
}
16+
17+
useContainer();
18+
19+
beforeEach(() => {
20+
value = createRef(null);
21+
});
22+
23+
it("translates", async() => {
24+
await ComponentHandler.setup(Component);
25+
26+
expect(value.current.translate("en-us", "tip_type_for_tools")).toEqual("Tools To Use");
27+
expect(value.current.translate("en-us", "results.reports.candidate")).toEqual("Candidate Report");
28+
expect(value.current.translate("en-us", "results.reports.manager")).toEqual("Hiring Manager Report");
29+
});
30+
31+
it("translates with remote translations", async() => {
32+
mockTranslations({
33+
"en-us": {
34+
results: {
35+
reports: {
36+
candidate: "Candidate",
37+
employee: "Unemployed Report"
38+
}
39+
},
40+
tip_type_for_tools: "Tools for Tots"
41+
}
42+
});
43+
await ComponentHandler.setup(Component);
44+
45+
expect(value.current.translate("en-us", "tip_type_for_tools")).toEqual("Tools for Tots");
46+
expect(value.current.translate("en-us", "results.reports.manager")).toEqual("Hiring Manager Report");
47+
});
48+
});

test/support/container/http.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,19 @@ export const mockSettings = (settings) => (
279279
})
280280
);
281281

282+
export const mockTranslations = (translations) => (
283+
mockFetch({
284+
key: "translate",
285+
request: (url, options) => {
286+
if(!url.includes("/xavier/translations")) { return false; }
287+
if(options.method !== "GET") { return false; }
288+
289+
return true;
290+
},
291+
response: () => translations
292+
})
293+
);
294+
282295
export const mockUserCompletedFeedback = (assessmentId, completed = false) => (
283296
mockFetch({
284297
key: "user-completed-feedback",
@@ -316,3 +329,6 @@ export const useHighlightedCareers = (...options) => {
316329
beforeEach(() => { mockHighlightedCareers(...options); });
317330
};
318331
export const useSettings = (...options) => { beforeEach(() => { mockSettings(...options); }); };
332+
export const useTranslations = (...options) => {
333+
beforeEach(() => { mockTranslations(...options); });
334+
};

test/support/hooks/use-container.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Cache from "lib/cache";
22
import Http from "lib/http";
33
import Listener from "lib/listener";
4+
import {useTranslations} from "../container/http";
45

56
const cacheMethods = ["get", "remove", "set"];
67
const httpMethods = ["delete", "fetch", "get", "post", "put", "request"];
@@ -41,6 +42,8 @@ export default function useContainer(props) {
4142
});
4243
});
4344

45+
useTranslations();
46+
4447
afterEach(() => {
4548
delete container.assessmentID;
4649
delete container.benchmarkID;

0 commit comments

Comments
 (0)