diff --git a/clover-hub/src/server/modman/components/audio/impls.rs b/clover-hub/src/server/modman/components/audio/impls.rs index db354ee..404825a 100644 --- a/clover-hub/src/server/modman/components/audio/impls.rs +++ b/clover-hub/src/server/modman/components/audio/impls.rs @@ -194,6 +194,7 @@ impl CloverComponentTrait for AudioOutputComponent { } } } + // TODO: ModMan Proxied Audio Output device (like a GROVE speaker). super::models::ConnectionType::ModManProxy(proxied_connection) => todo!(), super::models::ConnectionType::Stream(streaming_connection) => todo!(), } diff --git a/toolbox/ratchet/package.json b/toolbox/ratchet/package.json index 9616051..2e4a936 100644 --- a/toolbox/ratchet/package.json +++ b/toolbox/ratchet/package.json @@ -13,11 +13,16 @@ "lint": "next lint" }, "dependencies": { + "@tauri-apps/api": "^2.5.0", "@types/react": "^19.1.3", "@types/react-dom": "^19.1.3", + "i18next": "^25.2.0", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-resources-to-backend": "^1.2.1", "next": "^15.3.2", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-i18next": "^15.5.1", "tsc": "^2.0.4" } } diff --git a/toolbox/ratchet/src/app/index.tsx b/toolbox/ratchet/src/app/index.tsx new file mode 100644 index 0000000..aa00ed6 --- /dev/null +++ b/toolbox/ratchet/src/app/index.tsx @@ -0,0 +1,26 @@ +import { Namespace, FlatNamespace, KeyPrefix } from "i18next"; +import { FallbackNs } from "react-i18next"; +import i18next, { headerName } from "../utils/i18n"; + +type $Tuple = readonly [T?, ...T[]]; +type $FirstNamespace = Ns extends readonly any[] + ? Ns[0] + : Ns; + +export async function getT< + Ns extends FlatNamespace | $Tuple, + KPrefix extends KeyPrefix< + FallbackNs< + Ns extends FlatNamespace ? FlatNamespace : $FirstNamespace + > + > = undefined +>(ns?: Ns, options: { keyPrefix?: KPrefix } = {}) { + await i18next.changeLanguage(); + if (ns && !i18next.hasLoadedNamespace(ns as string | string[])) { + await i18next.loadNamespaces(ns as string | string[]); + } + return { + t: i18next.t, + i18n: i18next, + }; +} diff --git a/toolbox/ratchet/src/app/welcome/page.tsx b/toolbox/ratchet/src/app/welcome/page.tsx index 9c6fadc..de6c0ca 100644 --- a/toolbox/ratchet/src/app/welcome/page.tsx +++ b/toolbox/ratchet/src/app/welcome/page.tsx @@ -1,3 +1,13 @@ -export default function Page() { - return

Welcome Screen

; +import { getT } from "../index"; + +export default async function Page() { + const { t } = await getT(); + const version = require("../../../package.json").version; + + return ( + <> +

{t("welcome.hello")}

+

{t("welcome.version", { version })}

+ + ); } diff --git a/toolbox/ratchet/src/utils/i18n.ts b/toolbox/ratchet/src/utils/i18n.ts new file mode 100644 index 0000000..7da1e37 --- /dev/null +++ b/toolbox/ratchet/src/utils/i18n.ts @@ -0,0 +1,41 @@ +import i18next from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import resourcesToBackend from "i18next-resources-to-backend"; +// import LocizeBackend from 'i18next-locize-backend' +import { initReactI18next } from "react-i18next/initReactI18next"; + +export const fallbackLng = "en"; +export const languages = [fallbackLng, "es", "de"]; +export const defaultNS = "translation"; +export const cookieName = "i18next"; +export const headerName = "x-i18next-current-language"; + +const runsOnServerSide = typeof window === "undefined"; + +i18next + .use(initReactI18next) + .use(LanguageDetector) + .use( + resourcesToBackend( + (language: string, namespace: string) => + import(`./locales/${language}/${namespace}.json`) + ) + ) + // .use(runsOnServerSide ? LocizeBackend : resourcesToBackend((language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`))) // locize backend could be used, but prefer to keep it in sync with server side + .init({ + // debug: true, + supportedLngs: languages, + fallbackLng, + lng: undefined, // let detect the language on client side + fallbackNS: defaultNS, + defaultNS, + detection: { + order: ["path", "htmlTag", "cookie", "navigator"], + }, + preload: runsOnServerSide ? languages : [], + // backend: { + // projectId: '01b2e5e8-6243-47d1-b36f-963dbb8bcae3' + // } + }); + +export default i18next; diff --git a/toolbox/ratchet/src/utils/locales/en.json b/toolbox/ratchet/src/utils/locales/en.json new file mode 100644 index 0000000..b2aa89f --- /dev/null +++ b/toolbox/ratchet/src/utils/locales/en.json @@ -0,0 +1,6 @@ +{ + "welcome": { + "hello": "Hello there!", + "version": "You're running Ratchet v{{version}}." + } +}w diff --git a/toolbox/ratchet/src/utils/native.ts b/toolbox/ratchet/src/utils/native.ts new file mode 100644 index 0000000..e69de29 diff --git a/toolbox/ratchet/tsconfig.json b/toolbox/ratchet/tsconfig.json new file mode 100644 index 0000000..f619795 --- /dev/null +++ b/toolbox/ratchet/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/yarn.lock b/yarn.lock index 598b6d6..d35a60a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1711,7 +1711,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.9": +"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.9, @babel/runtime@npm:^7.27.1": version: 7.27.1 resolution: "@babel/runtime@npm:7.27.1" checksum: 10c0/530a7332f86ac5a7442250456823a930906911d895c0b743bf1852efc88a20a016ed4cd26d442d0ca40ae6d5448111e02a08dd638a4f1064b47d080e2875dc05 @@ -1786,13 +1786,18 @@ __metadata: version: 0.0.0-use.local resolution: "@clover/ratchet@workspace:toolbox/ratchet" dependencies: + "@tauri-apps/api": "npm:^2.5.0" "@tauri-apps/cli": "npm:^2.5.0" "@types/node": "npm:22.15.17" "@types/react": "npm:^19.1.3" "@types/react-dom": "npm:^19.1.3" + i18next: "npm:^25.2.0" + i18next-browser-languagedetector: "npm:^8.1.0" + i18next-resources-to-backend: "npm:^1.2.1" next: "npm:^15.3.2" react: "npm:^19.1.0" react-dom: "npm:^19.1.0" + react-i18next: "npm:^15.5.1" tsc: "npm:^2.0.4" typescript: "npm:^5.8.3" languageName: unknown @@ -4459,6 +4464,13 @@ __metadata: languageName: node linkType: hard +"@tauri-apps/api@npm:^2.5.0": + version: 2.5.0 + resolution: "@tauri-apps/api@npm:2.5.0" + checksum: 10c0/8eeb28049d48f5f89a5419cdf313bb159305204b193a5f27ebddbe0704ff43037c8b2e78518de831d7393e00178f0c0ac66cef1a57b99c632de71eca29f562c0 + languageName: node + linkType: hard + "@tauri-apps/cli-darwin-arm64@npm:2.5.0": version: 2.5.0 resolution: "@tauri-apps/cli-darwin-arm64@npm:2.5.0" @@ -10164,6 +10176,15 @@ __metadata: languageName: node linkType: hard +"html-parse-stringify@npm:^3.0.1": + version: 3.0.1 + resolution: "html-parse-stringify@npm:3.0.1" + dependencies: + void-elements: "npm:3.1.0" + checksum: 10c0/159292753d48b84d216d61121054ae5a33466b3db5b446e2ffc093ac077a411a99ce6cbe0d18e55b87cf25fa3c5a86c4d8b130b9719ec9b66623259000c72c15 + languageName: node + linkType: hard + "html-tags@npm:^3.3.1": version: 3.3.1 resolution: "html-tags@npm:3.3.1" @@ -10335,6 +10356,38 @@ __metadata: languageName: node linkType: hard +"i18next-browser-languagedetector@npm:^8.1.0": + version: 8.1.0 + resolution: "i18next-browser-languagedetector@npm:8.1.0" + dependencies: + "@babel/runtime": "npm:^7.23.2" + checksum: 10c0/d55162f8062e4fdca07403273ef352e7122e1f9abe479404c6711f5a9b75ddb4b33d49b5a50416637d3a3f0553881ba6a570062c8f6e6c52b031eceb0bb8669e + languageName: node + linkType: hard + +"i18next-resources-to-backend@npm:^1.2.1": + version: 1.2.1 + resolution: "i18next-resources-to-backend@npm:1.2.1" + dependencies: + "@babel/runtime": "npm:^7.23.2" + checksum: 10c0/ba83152726aae34ab34bb152518b666d4393bf816c91ab6e1064090cbbb9b235d3dfbcf9d5640c825819644fddcaf7799f581cdbe6abb3e7b11d58c50dc4644a + languageName: node + linkType: hard + +"i18next@npm:^25.2.0": + version: 25.2.0 + resolution: "i18next@npm:25.2.0" + dependencies: + "@babel/runtime": "npm:^7.27.1" + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/393d45b405c53edda3cb37e8882e1a8169d0b649578541e0bb6802d28ce9c260b503f49b5b08953ef4547dbccd5d8a9adcab51290dfad6844ca7390493e274eb + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -15616,6 +15669,27 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:^15.5.1": + version: 15.5.1 + resolution: "react-i18next@npm:15.5.1" + dependencies: + "@babel/runtime": "npm:^7.25.0" + html-parse-stringify: "npm:^3.0.1" + peerDependencies: + i18next: ">= 23.2.3" + react: ">= 16.8.0" + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + checksum: 10c0/448e190beb3338e5e6e48936c82b5e8610fdb54d0b3dafc14c2af76115a6c931eff601c3e4aee00b3d3c1b78d4d7061bbac443fc9bcff786d131e695c79f5822 + languageName: node + linkType: hard + "react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -18406,6 +18480,13 @@ __metadata: languageName: node linkType: hard +"void-elements@npm:3.1.0": + version: 3.1.0 + resolution: "void-elements@npm:3.1.0" + checksum: 10c0/0b8686f9f9aa44012e9bd5eabf287ae0cde409b9a2854c5a2335cb83920c957668ac5876e3f0d158dd424744ac411a7270e64128556b451ed3bec875ef18534d + languageName: node + linkType: hard + "walker@npm:^1.0.7, walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8"