diff --git a/bun.lockb b/bun.lockb index e8be32d..9652a01 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index aa19ecb..8730fd2 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "calque", "dependencies": { + "@solid-primitives/i18n": "^2.1.1", + "@solid-primitives/storage": "^4.2.1", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.15.2", "@solidjs/start": "^1.0.10", "dexie": "^4.0.10", + "flag-icons": "^7.2.3", "iterator-helpers-polyfill": "^3.0.1", "sitemap": "^8.0.0", "solid-icons": "^1.1.0", diff --git a/src/app.tsx b/src/app.tsx index 977a2cf..f47cf2b 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -2,8 +2,9 @@ import { MetaProvider } from "@solidjs/meta"; import { Router } from "@solidjs/router"; import { FileRoutes } from "@solidjs/start/router"; import { Suspense } from "solid-js"; -import "./app.css"; import { ThemeProvider } from "./components/colorschemepicker"; +import { I18nProvider } from "./features/i18n"; +import "./app.css"; export default function App() { return ( @@ -11,9 +12,11 @@ export default function App() { root={props => ( - {props.children} + + {props.children} + - + )} > diff --git a/src/features/i18n/constants.ts b/src/features/i18n/constants.ts new file mode 100644 index 0000000..8d26e30 --- /dev/null +++ b/src/features/i18n/constants.ts @@ -0,0 +1,9 @@ +import { Locale } from "./context"; + +import Flag_en_GB from 'flag-icons/flags/4x3/gb.svg'; +import Flag_nl_NL from 'flag-icons/flags/4x3/nl.svg'; + +export const locales: Record = { + 'en-GB': { label: 'English', flag: Flag_en_GB }, + 'nl-NL': { label: 'Nederlands', flag: Flag_nl_NL }, +} as const; \ No newline at end of file diff --git a/src/features/i18n/context.tsx b/src/features/i18n/context.tsx new file mode 100644 index 0000000..39f801a --- /dev/null +++ b/src/features/i18n/context.tsx @@ -0,0 +1,60 @@ +import { Accessor, createContext, createMemo, createSignal, ParentComponent, Setter, useContext } from 'solid-js'; +import { translator, flatten, Translator, Flatten } from "@solid-primitives/i18n"; +import en from '~/i18n/en-GB.json'; +import nl from '~/i18n/nl-NL.json'; +import { makePersisted } from '@solid-primitives/storage'; + +type RawDictionary = typeof en; +type Dictionary = Flatten; +export type Locale = 'en-GB' | 'nl-NL'; + +const dictionaries = { + 'en-GB': en, + 'nl-NL': nl, +} as const; + +interface I18nContextType { + readonly t: Translator; + readonly locale: Accessor; + readonly setLocale: Setter; + readonly dictionaries: Accessor>; + readonly availableLocales: Accessor; +} + +const I18nContext = createContext(); + +export const I18nProvider: ParentComponent = (props) => { + const [locale, setLocale, initLocale] = makePersisted(createSignal('en-GB'), { name: 'locale' }); + const dictionary = createMemo(() => flatten(dictionaries[locale()])); + const t = translator(dictionary); + + const ctx: I18nContextType = { + t, + locale, + setLocale, + dictionaries: createMemo(() => dictionaries), + availableLocales: createMemo(() => Object.keys(dictionaries) as Locale[]), + }; + + return {props.children} +}; + +export const useI18n = () => { + const context = useContext(I18nContext); + + if (!context) { + throw new Error(`'useI18n' is called outside the scope of an `); + } + + return { t: context.t, locale: context.locale }; +}; + +export const internal_useI18n = () => { + const context = useContext(I18nContext); + + if (!context) { + throw new Error(`'useI18n' is called outside the scope of an `); + } + + return context; +}; \ No newline at end of file diff --git a/src/features/i18n/index.tsx b/src/features/i18n/index.tsx new file mode 100644 index 0000000..38e40d4 --- /dev/null +++ b/src/features/i18n/index.tsx @@ -0,0 +1,2 @@ +export { I18nProvider, useI18n } from './context'; +export { LocalePicker } from './picker'; \ No newline at end of file diff --git a/src/features/i18n/picker.module.css b/src/features/i18n/picker.module.css new file mode 100644 index 0000000..5582a74 --- /dev/null +++ b/src/features/i18n/picker.module.css @@ -0,0 +1,7 @@ +.box { + grid-template-columns: 1fr; +} + +.flag { + inline-size: 1em; +} \ No newline at end of file diff --git a/src/features/i18n/picker.tsx b/src/features/i18n/picker.tsx new file mode 100644 index 0000000..bb811aa --- /dev/null +++ b/src/features/i18n/picker.tsx @@ -0,0 +1,22 @@ +import { Component } from "solid-js"; +import { internal_useI18n } from "./context"; +import { locales } from "./constants"; +import { ComboBox } from "~/components/combobox"; +import { Dynamic } from "solid-js/web"; +import css from './picker.module.css'; + +interface LocalePickerProps { } + +export const LocalePicker: Component = (props) => { + const { locale, setLocale } = internal_useI18n(); + + return + {(locale, { flag, label }) => } + +}; \ No newline at end of file diff --git a/src/i18n/en-GB.json b/src/i18n/en-GB.json new file mode 100644 index 0000000..39f6209 --- /dev/null +++ b/src/i18n/en-GB.json @@ -0,0 +1,37 @@ +{ + "page": { + "welcome": { + "title": "Hi, welcome!", + "subtitle": "Lets get started", + "edit": "Start editing", + "instructions": "Read the instructions", + "about": "Abut this app" + }, + "edit": { + "menu": { + "file": "File", + "edit": "Edit", + "selection": "Selection" + }, + "command": { + "open": "Open folder", + "close": "Close folder", + "closeTab": "Close tab", + "save": "Save", + "saveAs": "Save as ...", + "selectAll": "Select all", + "clearSelection": "Clear selection", + "insertKey": "Insert new key", + "insertLanguage": "Insert new language", + "delete": "Delete selected items" + } + } + }, + "feature": { + "file": { + "grid": { + "key": "Key" + } + } + } +} \ No newline at end of file diff --git a/src/i18n/nl-NL.json b/src/i18n/nl-NL.json new file mode 100644 index 0000000..b55871c --- /dev/null +++ b/src/i18n/nl-NL.json @@ -0,0 +1,37 @@ +{ + "page": { + "welcome": { + "title": "Hoi, welkom!", + "subtitle": "Laten we beginnen", + "edit": "Begin met bewerken", + "instructions": "Lees de instructies", + "about": "Over deze app" + }, + "edit": { + "menu": { + "file": "Bestand", + "edit": "Bewerken", + "selection": "Selectie" + }, + "command": { + "open": "Map openen", + "close": "Map sluiten", + "closeTab": "Tabblad sluiten", + "save": "Opslaan", + "saveAs": "Opslaan als ...", + "selectAll": "Selecteer alles", + "clearSelection": "Selectie leeg maken", + "insertKey": "Voeg nieuwe sleutel toe", + "insertLanguage": "Voeg nieuwe taal toe", + "delete": "Verwijder geselecteerde items" + } + } + }, + "feature": { + "file": { + "grid": { + "key": "Sleutel" + } + } + } +} \ No newline at end of file