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}
+
-
+ MetaProvider>
)}
>
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