From 663f90a164d9faa6e65aa78323f066d4e3506ad6 Mon Sep 17 00:00:00 2001 From: Jan Simson Date: Thu, 27 Jul 2023 14:33:37 +0200 Subject: [PATCH] Add first example implementation of basic translation --- packages/jspsych/src/JsPsych.ts | 53 +++++++++++++++++ packages/jspsych/tests/core/i18n.test.ts | 73 ++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 packages/jspsych/tests/core/i18n.test.ts diff --git a/packages/jspsych/src/JsPsych.ts b/packages/jspsych/src/JsPsych.ts index b1395318dd..8fbdd7dad3 100644 --- a/packages/jspsych/src/JsPsych.ts +++ b/packages/jspsych/src/JsPsych.ts @@ -86,6 +86,11 @@ export class JsPsych { // of jsPsych webaudio_context: AudioContext = null; + /** + * the currently active locale (if specified) + */ + locale: string | undefined = undefined; + internal = { /** * this flag is used to determine whether we are in a scope where @@ -117,10 +122,15 @@ export class JsPsych { override_safe_mode: false, case_sensitive_responses: false, extensions: [], + translations: {}, + locale: undefined, + fallback_locale: undefined, ...options, }; this.opts = options; + this.locale = this.opts.locale; + autoBind(this); // so we can pass JsPsych methods as callbacks and `this` remains the JsPsych instance this.webaudio_context = @@ -379,6 +389,49 @@ export class JsPsych { return this.timeline.allTimelineVariables(); } + setLocale(locale: string) { + this.locale = locale; + } + + translate(key: string) { + // TODO: This should support string processing + maybe more i18n features + + // First pass at translation using the current locale + const locale = this.locale; + if (locale) { + const translation = this.searchTranslation(locale, key); + if (translation !== undefined) { + return translation; + } + } + // Second pass using the fallback_locale + const fallbackLocale = this.opts.fallback_locale; + if (fallbackLocale) { + const translation = this.searchTranslation(fallbackLocale, key); + if (translation !== undefined) { + return translation; + } + } + // If nothing is found, default to return the original translation key + return key; + } + + private searchTranslation(locale: string, key: string) { + // First search for translations on the trial itself + if ( + this.current_trial.translations && + locale in this.current_trial.translations && + key in this.current_trial.translations[locale] + ) { + return this.current_trial.translations[locale][key]; + } + // Then search for global translations + if (locale in this.opts.translations && key in this.opts.translations?.[locale]) { + return this.opts.translations[locale][key]; + } + return undefined; + } + addNodeToEndOfTimeline(new_timeline, preload_callback?) { this.timeline.insert(new_timeline); } diff --git a/packages/jspsych/tests/core/i18n.test.ts b/packages/jspsych/tests/core/i18n.test.ts new file mode 100644 index 0000000000..3357b9c7e6 --- /dev/null +++ b/packages/jspsych/tests/core/i18n.test.ts @@ -0,0 +1,73 @@ +import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; +import { pressKey, startTimeline } from "@jspsych/test-utils"; + +import { initJsPsych } from "../../src"; + +describe("translations are correctly fetched", () => { + test("when drawing from the global translations", async () => { + const jsPsych = initJsPsych({ + locale: "de", + translations: { + de: { + "Hello World!": "Hallo Welt!", + }, + }, + }); + const { getHTML } = await startTimeline( + [ + { + type: htmlKeyboardResponse, + stimulus: () => jsPsych.translate("Hello World!"), + on_finish: () => { + jsPsych.setLocale("en"); + }, + }, + { + type: htmlKeyboardResponse, + stimulus: () => jsPsych.translate("Hello World!"), + }, + ], + jsPsych + ); + + expect(getHTML()).toMatch("Hallo Welt!"); + pressKey("a"); + expect(getHTML()).toMatch("Hello World!"); + }); + + test("when drawing from trial-level translations", async () => { + const jsPsych = initJsPsych({ + locale: "de", + }); + const { getHTML } = await startTimeline( + [ + { + type: htmlKeyboardResponse, + stimulus: () => jsPsych.translate("Hello World!"), + translations: { + de: { + "Hello World!": "Hallo Welt!", + }, + }, + on_finish: () => { + jsPsych.setLocale("en"); + }, + }, + { + type: htmlKeyboardResponse, + stimulus: () => jsPsych.translate("Hello World!"), + translations: { + de: { + "Hello World!": "Hallo Welt!", + }, + }, + }, + ], + jsPsych + ); + + expect(getHTML()).toMatch("Hallo Welt!"); + pressKey("a"); + expect(getHTML()).toMatch("Hello World!"); + }); +});