diff --git a/packages/record/src/consentVideo.spec.ts b/packages/record/src/consentVideo.spec.ts index 1ad76c7e..fa048dd2 100644 --- a/packages/record/src/consentVideo.spec.ts +++ b/packages/record/src/consentVideo.spec.ts @@ -42,7 +42,10 @@ test("Trial", () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; plugin["recordFeed"] = jest.fn(); plugin["recordButton"] = jest.fn(); @@ -116,7 +119,10 @@ test("onEnded", () => { const play = document.createElement("button"); const next = document.createElement("button"); const record = document.createElement("button"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; display.innerHTML = chsTemplates.consentVideo(trial); plugin["recordFeed"] = jest.fn(); @@ -152,7 +158,10 @@ test("getButton", () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; display.innerHTML = chsTemplates.consentVideo(trial); @@ -182,7 +191,10 @@ test("recordButton", async () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; display.innerHTML = chsTemplates.consentVideo(trial); @@ -228,7 +240,10 @@ test("playButton", () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; plugin["playbackFeed"] = jest.fn(); @@ -249,7 +264,10 @@ test("stopButton", async () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; display.innerHTML = chsTemplates.consentVideo(trial) + Handlebars.compile(recordFeed)({}); @@ -274,7 +292,10 @@ test("nextButton", () => { const jsPsych = initJsPsych(); const plugin = new VideoConsentPlugin(jsPsych); const display = document.createElement("div"); - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; display.innerHTML = chsTemplates.consentVideo(trial); plugin["endTrial"] = jest.fn(); diff --git a/packages/templates/src/consentVideoTemplate.ts b/packages/templates/src/consentVideoTemplate.ts index ff28ed23..63870f04 100644 --- a/packages/templates/src/consentVideoTemplate.ts +++ b/packages/templates/src/consentVideoTemplate.ts @@ -4,7 +4,7 @@ import { PluginInfo, TrialType } from "jspsych"; import consent_template_5 from "../hbs/consent-template-5.hbs"; import consentVideoTrialTemplate from "../hbs/consent-video-trial.hbs"; import { ConsentTemplateNotFound } from "./errors"; -import { initI18nAndHelpers } from "./utils"; +import { setLocale } from "./utils"; declare const window: LookitWindow; @@ -20,7 +20,8 @@ export const consentVideo = (trial: TrialType) => { const experiment = window.chs.study.attributes; const { PIName, PIContact } = trial; - initI18nAndHelpers(trial); + setLocale(trial); + const consentDocumentTemplate = consentDocument(trial); const consent = Handlebars.compile(consentDocumentTemplate)({ diff --git a/packages/templates/src/errors.ts b/packages/templates/src/errors.ts index 274354a0..07ddaa58 100644 --- a/packages/templates/src/errors.ts +++ b/packages/templates/src/errors.ts @@ -1,12 +1,12 @@ -/** Error throw what specified language isn't found */ -export class TranslationNotFoundError extends Error { +/** Error thrown when specified language isn't found */ +export class LocaleNotFoundError extends Error { /** * This will be thrown when attempting to init i18n * * @param baseName - Language a2code with region */ public constructor(baseName: string) { - super(`"${baseName}" translation not found.`); + super(`"${baseName}" locale not found.`); } } /** Error thrown when researcher selects template that isn't available. */ diff --git a/packages/templates/src/index.spec.ts b/packages/templates/src/index.spec.ts index 807d9db8..1aaa6459 100644 --- a/packages/templates/src/index.spec.ts +++ b/packages/templates/src/index.spec.ts @@ -1,11 +1,15 @@ import { LookitWindow } from "@lookit/data/dist/types"; import { PluginInfo, TrialType } from "jspsych"; +import { ConsentTemplateNotFound } from "./errors"; import chsTemplate from "./index"; declare const window: LookitWindow; test("consent video", () => { - const trial = { locale: "en-us" } as unknown as TrialType; + const trial = { + locale: "en-us", + template: "consent-template-5", + } as unknown as TrialType; const name = "some name"; window.chs = { study: { @@ -25,7 +29,10 @@ test("consent video", () => { }); test("consent video in French", () => { - const trial = { locale: "fr" } as unknown as TrialType; + const trial = { + locale: "fr", + template: "consent-template-5", + } as unknown as TrialType; const name = "some name"; window.chs = { study: { @@ -43,3 +50,13 @@ test("consent video in French", () => { `Consentement à participer à la recherche:\n ${name}`, ); }); + +test("consent video with unknown template", () => { + const trial = { + locale: "en-us", + template: "not a real template name", + } as unknown as TrialType; + expect(() => chsTemplate.consentVideo(trial)).toThrow( + ConsentTemplateNotFound, + ); +}); diff --git a/packages/templates/src/utils.spec.ts b/packages/templates/src/utils.spec.ts index 6bd4e4ab..922a399a 100644 --- a/packages/templates/src/utils.spec.ts +++ b/packages/templates/src/utils.spec.ts @@ -1,15 +1,4 @@ -import Yaml from "js-yaml"; -import en_us from "../i18n/en-us.yaml"; -import eu from "../i18n/eu.yaml"; -import fr from "../i18n/fr.yaml"; -import hu from "../i18n/hu.yaml"; -import it from "../i18n/it.yaml"; -import ja from "../i18n/ja.yaml"; -import nl from "../i18n/nl.yaml"; -import pt_br from "../i18n/pt-br.yaml"; -import pt from "../i18n/pt.yaml"; -import { TranslationNotFoundError } from "./errors"; -import { expFormat, getTranslation } from "./utils"; +import { expFormat } from "./utils"; test("expFormat convert written text to format well in HTML", () => { expect(expFormat("abcdefg")).toStrictEqual("abcdefg"); @@ -29,27 +18,3 @@ test("expFormat convert written text to format well in HTML", () => { "    Tabbed text", ); }); - -test("Get translation file for specified locale", () => { - const translations = { - ja, - pt, - eu, - fr, - hu, - it, - nl, - "en-us": en_us, - "pt-br": pt_br, - }; - - for (const [k, v] of Object.entries(translations)) { - expect(getTranslation(new Intl.Locale(k))).toStrictEqual(Yaml.load(v)); - } - - expect(pt_br).not.toStrictEqual(pt); - - expect(() => getTranslation(new Intl.Locale("not-a2code"))).toThrow( - TranslationNotFoundError, - ); -}); diff --git a/packages/templates/src/utils.ts b/packages/templates/src/utils.ts index caa1263d..1626e10b 100644 --- a/packages/templates/src/utils.ts +++ b/packages/templates/src/utils.ts @@ -12,10 +12,7 @@ import ja from "../i18n/ja.yaml"; import nl from "../i18n/nl.yaml"; import pt_br from "../i18n/pt-br.yaml"; import pt from "../i18n/pt.yaml"; -import { TranslationNotFoundError } from "./errors"; - -// TODO: Can this be in the global context. -Handlebars.registerHelper("exp-format", (context) => expFormat(context)); +import { LocaleNotFoundError } from "./errors"; /** * Pulled from EFP. Function to convert researcher's text to HTML. @@ -38,45 +35,32 @@ export const expFormat = (text?: string | string[]) => { }; /** - * Get a translation file based on selected language. + * Get a translation resources from yaml files. * - * @param lcl - Locale object with locale - * @returns Translations from i18next + * @returns Resources for i18next */ -export const getTranslation = (lcl: Intl.Locale) => { - /** - * Switch case to find language from a string. Will throw error is language - * not found. - * - * @param baseName - Base name from locale (en-us) - * @returns Language yaml file - */ - const getYaml = (baseName: string) => { - switch (baseName) { - case "en-US": - return en_us; - case "eu": - return eu; - case "fr": - return fr; - case "hu": - return hu; - case "it": - return it; - case "ja": - return ja; - case "nl": - return nl; - case "pt-BR": - return pt_br; - case "pt": - return pt; - default: - throw new TranslationNotFoundError(baseName); - } +const resources = () => { + const translations = { + "en-us": en_us, + eu, + fr, + hu, + it, + ja, + nl, + "pt-br": pt_br, + pt, }; - return Yaml.load(getYaml(lcl.baseName)) as Record; + return Object.entries(translations).reduce((prev, [locale, translation]) => { + const lcl = new Intl.Locale(locale); + return { + ...prev, + [lcl.baseName]: { + translation: Yaml.load(translation) as Record, + }, + }; + }, {}); }; /** @@ -84,39 +68,27 @@ export const getTranslation = (lcl: Intl.Locale) => { * * @param trial - Trial data including user supplied parameters. */ -const initI18next = (trial: TrialType) => { - const debug = process.env.DEBUG === "true"; - // TODO: Catch Locale error when bad locale - const lcl = new Intl.Locale(trial.locale); - const translation = getTranslation(lcl); - - i18next.use(ICU).init({ - lng: lcl.baseName, - debug, - resources: { - [lcl.language]: { - translation, - }, - }, - }); +export const setLocale = (trial: TrialType) => { + try { + const lcl = new Intl.Locale(trial.locale); + if (i18next.language !== lcl.baseName) { + i18next.changeLanguage(lcl.baseName); + } + } catch (error) { + if (error instanceof RangeError) { + throw new LocaleNotFoundError(trial.locale); + } else { + throw error; + } + } }; -/** - * Initialize handlebars helpers. This could be done globally, but it does go - * hand in hand with initializing i18n. - */ -const initHandlebars = () => { - Handlebars.registerHelper("t", (context, { hash }) => - i18next.t(context, hash), - ); -}; +// Initialize translations +i18next.use(ICU).init({ + debug: process.env.DEBUG === "true", + resources: resources(), +}); -/** - * Initialize both i18next and Handlebars. - * - * @param trial - Yup - */ -export const initI18nAndHelpers = (trial: TrialType) => { - initI18next(trial); - initHandlebars(); -}; +// Setup Handlebars' helpers +Handlebars.registerHelper("exp-format", (context) => expFormat(context)); +Handlebars.registerHelper("t", (context, { hash }) => i18next.t(context, hash));