diff --git a/src/i18n/de_DE.ts b/src/i18n/de_DE.ts new file mode 100644 index 000000000..f8d1553f1 --- /dev/null +++ b/src/i18n/de_DE.ts @@ -0,0 +1,12 @@ +export default { + buttons: { + next: "Weiter", + prev: "Zurück", + skip: "Überspringen", + done: "Fertig", + }, + messages: { + dontShowAgainLabel: "Nicht wieder anzeigen", + stepNumbersOfLabel: "von", + }, +}; diff --git a/src/i18n/en_US.ts b/src/i18n/en_US.ts new file mode 100644 index 000000000..3e9efb7b3 --- /dev/null +++ b/src/i18n/en_US.ts @@ -0,0 +1,12 @@ +export default { + buttons: { + next: "Next", + prev: "Back", + skip: "Skip", + done: "Done", + }, + messages: { + dontShowAgainLabel: "Don't show this again", + stepNumbersOfLabel: "of", + }, +}; diff --git a/src/i18n/es_ES.ts b/src/i18n/es_ES.ts new file mode 100644 index 000000000..f4d16d08d --- /dev/null +++ b/src/i18n/es_ES.ts @@ -0,0 +1,12 @@ +export default { + buttons: { + next: "Siguiente", + prev: "Atrás", + skip: "Saltar", + done: "Hecho", + }, + messages: { + dontShowAgainLabel: "No mostrar esto de nuevo", + stepNumbersOfLabel: "de", + }, +}; diff --git a/src/i18n/fa_IR.ts b/src/i18n/fa_IR.ts new file mode 100644 index 000000000..f20dae331 --- /dev/null +++ b/src/i18n/fa_IR.ts @@ -0,0 +1,12 @@ +export default { + buttons: { + next: "بعدی", + prev: "قبلی", + skip: "رد کردن", + done: "پایان", + }, + messages: { + dontShowAgainLabel: "دیگر این را نشان نده", + stepNumbersOfLabel: "از", + }, +}; diff --git a/src/i18n/fr_FR.ts b/src/i18n/fr_FR.ts new file mode 100644 index 000000000..228ff0ff9 --- /dev/null +++ b/src/i18n/fr_FR.ts @@ -0,0 +1,12 @@ +export default { + buttons: { + next: "Suivant", + prev: "Retour", + skip: "Passer", + done: "Terminé", + }, + messages: { + dontShowAgainLabel: "Ne plus afficher ceci", + stepNumbersOfLabel: "sur", + }, +}; diff --git a/src/i18n/language.ts b/src/i18n/language.ts new file mode 100644 index 000000000..c5278321f --- /dev/null +++ b/src/i18n/language.ts @@ -0,0 +1,67 @@ +import enUS from "./en_US"; +import faIR from "./fa_IR"; +import de_DE from "./de_DE"; +import esES from "./es_ES"; +import frFR from "./fr_FR"; + +type MessageFormat = (...args: any[]) => string; +type Message = string | MessageFormat; +export type Language = { [key: string]: Message | Language }; + +const languages: Record = { + en_US: enUS, + fa_IR: faIR, + de_DE: de_DE, + es_ES: esES, + fr_FR: frFR, +}; + +export class Translator { + private _language: Language; + + constructor(language?: Language) { + if (language) { + this._language = language; + } else { + const rawLang = + (navigator.language || (navigator as any).userLanguage || "en-US") + .replace("-", "_"); + + const normalizedLang = Object.keys(languages).find( + key => key.toLowerCase() === rawLang.toLowerCase() + ); + + this._language = normalizedLang ? languages[normalizedLang] : enUS; + } + } + + setLanguage(language: Language) { + this._language = language; + } + + private getString(message: string, lang: Language = this._language): MessageFormat | null { + if (!lang || !message) return null; + + const splitted = message.split("."); + const key = splitted[0]; + + if (lang[key]) { + const val = lang[key]; + + if (typeof val === "string") { + return (): string => val; + } else if (typeof val === "function") { + return val; + } else { + return this.getString(splitted.slice(1).join("."), val as Language); + } + } + + return null; + } + + translate(message: string, ...args: any[]): string { + const translated = this.getString(message); + return translated ? translated(...args) : message; + } +} diff --git a/src/packages/tour/option.ts b/src/packages/tour/option.ts index f24c904f2..8fc6a74ef 100644 --- a/src/packages/tour/option.ts +++ b/src/packages/tour/option.ts @@ -1,5 +1,7 @@ import { TooltipPosition } from "../../packages/tooltip"; import { TourStep, ScrollTo } from "./steps"; +import { Translator, Language } from "../../i18n/language"; +import enUS from "../../i18n/en_US"; export interface TourOptions { steps: Partial[]; @@ -74,16 +76,20 @@ export interface TourOptions { progressBarAdditionalClass: string; /* Optional property to determine if content should be rendered as HTML */ tooltipRenderAsHtml?: boolean; + /* Optional property to set the language of the tour. + Defaults to the user's browser language if not provided. */ + language?: Language; } -export function getDefaultTourOptions(): TourOptions { +export function getDefaultTourOptions(language: Language = enUS): TourOptions { + const translator = new Translator(language); return { steps: [], isActive: true, - nextLabel: "Next", - prevLabel: "Back", + nextLabel: translator.translate("buttons.next"), + prevLabel: translator.translate("buttons.prev"), skipLabel: "×", - doneLabel: "Done", + doneLabel: translator.translate("buttons.done"), hidePrev: false, hideNext: false, nextToDone: true, @@ -94,7 +100,7 @@ export function getDefaultTourOptions(): TourOptions { exitOnEsc: true, exitOnOverlayClick: true, showStepNumbers: false, - stepNumbersOfLabel: "of", + stepNumbersOfLabel: translator.translate("messages.stepNumbersOfLabel"), keyboardNavigation: true, showButtons: true, showBullets: true, @@ -108,7 +114,7 @@ export function getDefaultTourOptions(): TourOptions { disableInteraction: false, dontShowAgain: false, - dontShowAgainLabel: "Don't show this again", + dontShowAgainLabel: translator.translate("messages.dontShowAgain"), dontShowAgainCookie: "introjs-dontShowAgain", dontShowAgainCookieDays: 365, helperElementPadding: 10, @@ -116,5 +122,6 @@ export function getDefaultTourOptions(): TourOptions { buttonClass: "introjs-button", progressBarAdditionalClass: "", tooltipRenderAsHtml: true, + language, }; }