From d76b47e4a817aaa2623b042dbe8d1cc66326c14b Mon Sep 17 00:00:00 2001 From: Nils Date: Tue, 1 Oct 2024 17:09:20 +0200 Subject: [PATCH] add useI18n composable --- .../components/theme/mt-theme-provider.vue | 9 ++-- .../src/composables/useI18n.spec.ts | 34 ++++++++++++ .../src/composables/useI18n.ts | 53 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 packages/component-library/src/composables/useI18n.spec.ts create mode 100644 packages/component-library/src/composables/useI18n.ts diff --git a/packages/component-library/src/components/theme/mt-theme-provider.vue b/packages/component-library/src/components/theme/mt-theme-provider.vue index 9a03a435..0aee951f 100644 --- a/packages/component-library/src/components/theme/mt-theme-provider.vue +++ b/packages/component-library/src/components/theme/mt-theme-provider.vue @@ -3,13 +3,14 @@ diff --git a/packages/component-library/src/composables/useI18n.spec.ts b/packages/component-library/src/composables/useI18n.spec.ts new file mode 100644 index 00000000..f739cfa6 --- /dev/null +++ b/packages/component-library/src/composables/useI18n.spec.ts @@ -0,0 +1,34 @@ +import { render, screen } from "@testing-library/vue"; +import { useI18n } from "./useI18n"; + +describe("useI18n", () => { + it("returns the translation for the given path", () => { + render({ + setup() { + const { t } = useI18n({ + messages: { en: { greeting: "Hello!" } }, + }); + + return { t }; + }, + template: "{{ t('greeting') }}", + }); + + expect(screen.getByText("Hello!")).toBeInTheDocument(); + }); + + it("returns the path to the translation if no translation is found", () => { + render({ + setup() { + const { t } = useI18n({ + messages: { en: { greeting: "Hello!" } }, + }); + + return { t }; + }, + template: "{{ t('path.to.translation') }}", + }); + + expect(screen.getByText("path.to.translation")).toBeInTheDocument(); + }); +}); diff --git a/packages/component-library/src/composables/useI18n.ts b/packages/component-library/src/composables/useI18n.ts new file mode 100644 index 00000000..f8ac95ef --- /dev/null +++ b/packages/component-library/src/composables/useI18n.ts @@ -0,0 +1,53 @@ +import { provide, inject } from "vue"; + +function get(obj: Record, path: string) { + if (typeof obj !== "object" || obj === null) return undefined; + + const keys = path.split("."); + let result = obj; + + for (const key of keys) { + if (result === undefined || result === null) return undefined; + + // @ts-ignore + result = result[key]; + } + + return result; +} + +interface TranslationDictionary { + [key: string]: string | TranslationDictionary; +} + +type Options = { messages: TranslationDictionary }; + +const defaultI18nState = { + locale: "en", + defaultLocale: "en", +}; + +const i18nInjectionKey = Symbol("mt-i18n"); + +export function provideI18n(locale: string = "en") { + const state = { + ...defaultI18nState, + locale: locale, + }; + + provide(i18nInjectionKey, state); +} + +export function useI18n({ messages }: Options) { + const i18n = inject(i18nInjectionKey, defaultI18nState); + + function translate(path: string): string { + const translation = get(messages, `${i18n.locale}.${path}`); + if (translation) return translation.toString(); + + const fallback = get(messages, `${i18n.defaultLocale}.${path}`); + return fallback ? fallback.toString() : path; + } + + return { t: translate }; +}