From 9668ee2b87f8f69281f5f672f69254f862b33a47 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Tue, 23 Sep 2025 23:04:33 +0200 Subject: [PATCH 01/17] Begin working on some kind of i18n implementation --- src/apps/core/bootscreen/runtime.ts | 6 +- .../core/initialsetup/InitialSetup.svelte | 2 +- .../InitialSetup/Page/CheckInbox.svelte | 8 +- .../InitialSetup/Page/Finish.svelte | 8 +- .../InitialSetup/Page/FreshDeployment.svelte | 13 +- .../InitialSetup/Page/Identity.svelte | 44 ++--- .../InitialSetup/Page/License.svelte | 6 +- .../InitialSetup/Page/Welcome.svelte | 9 +- .../core/initialsetup/initialSetupWizard.ts | 2 +- src/apps/core/initialsetup/runtime.ts | 69 ++++---- src/apps/core/loginapp/ErrorMessage.svelte | 2 +- src/apps/core/loginapp/LoginForm.svelte | 8 +- src/apps/core/loginapp/runtime.ts | 64 +++---- src/lang/en.json | 147 +++++++++++++++ src/lang/nl.json | 147 +++++++++++++++ src/ts/kernel/init.ts | 4 + src/ts/kernel/mods/i18n/index.ts | 167 ++++++++++++++++++ src/ts/kernel/module/store.ts | 2 + tsconfig.app.json | 3 +- vite.config.ts | 1 + 20 files changed, 581 insertions(+), 131 deletions(-) create mode 100644 src/lang/en.json create mode 100644 src/lang/nl.json create mode 100644 src/ts/kernel/mods/i18n/index.ts diff --git a/src/apps/core/bootscreen/runtime.ts b/src/apps/core/bootscreen/runtime.ts index b0c8dc935..ccf145747 100755 --- a/src/apps/core/bootscreen/runtime.ts +++ b/src/apps/core/bootscreen/runtime.ts @@ -21,7 +21,7 @@ export class BootScreenRuntime extends AppProcess { async begin() { this.Log("Initializing boot"); - this.status.set("Press a key or click to start"); + this.status.set("%apps.bootScreen.pressAnyKey%"); document.addEventListener("click", () => this.startBooting(), { once: true, @@ -42,11 +42,11 @@ export class BootScreenRuntime extends AppProcess { this.progress.set(true); if (e?.key === "F8") { - this.status.set("Entering Safe Mode"); + this.status.set("%apps.bootScreen.safeMode%"); await Sleep(2000); KernelStateHandler()?.loadState("login", { safeMode: true }); } else if (e?.key.toLowerCase() === "a") { - this.status.set("Starting ArcTerm"); + this.status.set("%apps.bootScreen.arcTerm%"); KernelStateHandler()?.loadState("arcterm"); } else { this.status.set(" "); diff --git a/src/apps/core/initialsetup/InitialSetup.svelte b/src/apps/core/initialsetup/InitialSetup.svelte index 2cfc3885d..3c1fa65f7 100755 --- a/src/apps/core/initialsetup/InitialSetup.svelte +++ b/src/apps/core/initialsetup/InitialSetup.svelte @@ -26,7 +26,7 @@
-
+
{#if PageComponent} {/if} diff --git a/src/apps/core/initialsetup/InitialSetup/Page/CheckInbox.svelte b/src/apps/core/initialsetup/InitialSetup/Page/CheckInbox.svelte index 85bbdbd0b..2fc51e5d3 100755 --- a/src/apps/core/initialsetup/InitialSetup/Page/CheckInbox.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/CheckInbox.svelte @@ -4,9 +4,7 @@
-

Check your inbox

-

You've got mail!

-

- We sent you a link to activate your account. Open it to continue. Be sure to check your spam if you can’t find it. -

+

%checkInbox.title%

+

%checkInbox.subtitle%

+

%checkInbox.message%

diff --git a/src/apps/core/initialsetup/InitialSetup/Page/Finish.svelte b/src/apps/core/initialsetup/InitialSetup/Page/Finish.svelte index 7cf2fa97a..d26fbb42c 100755 --- a/src/apps/core/initialsetup/InitialSetup/Page/Finish.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/Finish.svelte @@ -4,9 +4,7 @@
-

All finished!

-

You're all set.

-

- Your account has been set up successfully. Click Finish to start using ArcOS. -

+

%finish.title%

+

%finish.subtitle%

+

%finish.message%

diff --git a/src/apps/core/initialsetup/InitialSetup/Page/FreshDeployment.svelte b/src/apps/core/initialsetup/InitialSetup/Page/FreshDeployment.svelte index d060d84bd..5bfa8870c 100644 --- a/src/apps/core/initialsetup/InitialSetup/Page/FreshDeployment.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/FreshDeployment.svelte @@ -5,19 +5,12 @@
-

Fresh deployment

-

- It appears this ReArc deployment is fresh. Before continuing, please make sure that that's supposed to be the case. If it is, - click Next. The user you're about to create is considered user #0, and will be set up as a God Admin by default. The - owner of this account is then automatically seen as the owner of the server. Also, please check the server configuration: -

+

%freshDeployment.title%

+

%freshDeployment.message%

    {#each Object.entries(process.server?.serverInfo!) as [key, value]}
  • {key}: {JSON.stringify(value)}
  • {/each}
-

- Note: disableRegistration is ignored for the first user. This configuration option will be retained once the - first user has been created. -

+

%freshDeployment.note%

diff --git a/src/apps/core/initialsetup/InitialSetup/Page/Identity.svelte b/src/apps/core/initialsetup/InitialSetup/Page/Identity.svelte index 843084a2f..c85b625b6 100755 --- a/src/apps/core/initialsetup/InitialSetup/Page/Identity.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/Identity.svelte @@ -2,7 +2,7 @@ import HtmlSpinner from "$lib/HtmlSpinner.svelte"; import { Sleep } from "$ts/sleep"; import { checkPasswordStrength, validateEmail, validateUsername } from "$ts/util"; - import { PasswordStrengthCaptions, type PasswordStrength } from "$types/user"; + import { type PasswordStrength } from "$types/user"; import type { InitialSetupRuntime } from "../../runtime"; const { process }: { process: InitialSetupRuntime } = $props(); @@ -120,15 +120,15 @@
-

Your ArcOS Identity

-

We'll use this information to create your ArcOS account.

+

%identity.title%

+

%identity.subtitle%

-

Display Name

+

%identity.fields.displayName%

-

Username

+

%identity.fields.username%

-

Password

+

%identity.fields.password%

- +
{#if usernameTaken && emailTaken} -

Username and email address are both taken!

+

%identity.errors.usernameAndEmailTaken%

{:else if usernameTaken} -

Username is already taken!

+

%identity.errors.usernameTaken%

{:else if emailTaken} -

Email is already taken!

+

%identity.errors.emailTaken%

{/if} {#if emailInvalid && usernameInvalid} -

Username and email address are both invalid!

+

%identity.errors.usernameAndEmailInvalid%

{:else if usernameInvalid} -

Username is invalid!

+

%identity.errors.usernameInvalid%

{:else if emailInvalid} -

Email address is invalid!

+

%identity.errors.emailInvalid%

{/if} {#if passwordInvalid} -

- Password is {PasswordStrengthCaptions[passwordStrength]}! -

+

%identity.errors.password{passwordStrength}%

{/if}
-

- * You will receive an email with a link to activate your account. Your display name, username and password can be changed - later on. To change your email, contact an administrator. -

+

%identity.disclaimer%

diff --git a/src/apps/core/initialsetup/InitialSetup/Page/License.svelte b/src/apps/core/initialsetup/InitialSetup/Page/License.svelte index 9bc150097..5ffd1fa71 100755 --- a/src/apps/core/initialsetup/InitialSetup/Page/License.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/License.svelte @@ -4,7 +4,7 @@
-

License Agreement

-

Scary stuff, I know

-

By using ArcOS, you agree to the GPLv3 license.

+

%license.title%

+

%license.subtitle%

+

%license.message%

diff --git a/src/apps/core/initialsetup/InitialSetup/Page/Welcome.svelte b/src/apps/core/initialsetup/InitialSetup/Page/Welcome.svelte index 84d3393a9..4233e295f 100755 --- a/src/apps/core/initialsetup/InitialSetup/Page/Welcome.svelte +++ b/src/apps/core/initialsetup/InitialSetup/Page/Welcome.svelte @@ -4,10 +4,7 @@
-

Welcome

-

We're happy to have you

-

- Let's get you an ArcOS account. Click Next to get started, or - Cancel to go back to the login screen. -

+

%welcome.title%

+

%welcome.subtitle%

+

%welcome.message%

diff --git a/src/apps/core/initialsetup/initialSetupWizard.ts b/src/apps/core/initialsetup/initialSetupWizard.ts index 1ff53062d..7f37054e8 100644 --- a/src/apps/core/initialsetup/initialSetupWizard.ts +++ b/src/apps/core/initialsetup/initialSetupWizard.ts @@ -6,7 +6,7 @@ import { InitialSetupRuntime } from "./runtime"; export const InitialSetupWizard: App = { metadata: { - name: "Initial Setup Wizard", + name: "%apps.initialSetupWizard._name%", author: "Izaak Kuipers", version: "7.0.0", icon: WaveIcon, diff --git a/src/apps/core/initialsetup/runtime.ts b/src/apps/core/initialsetup/runtime.ts index e5776bb7c..69bc8e5dc 100755 --- a/src/apps/core/initialsetup/runtime.ts +++ b/src/apps/core/initialsetup/runtime.ts @@ -39,7 +39,7 @@ export class InitialSetupRuntime extends AppProcess { public readonly pageButtons: PageButtons = [ { left: { - caption: "Cancel", + caption: "%general.cancel%", action: async () => { KernelStateHandler()?.loadState("login"); }, @@ -47,37 +47,37 @@ export class InitialSetupRuntime extends AppProcess { }, previous: { disabled: () => true, - caption: "Previous", + caption: "%general.previous%", to: 0, }, next: { suggested: true, - caption: "Next", + caption: "%general.next%", to: 1, }, }, { left: { - caption: "View License", + caption: "%apps.initialSetupWizard.buttons.viewLicense%", action: () => this.viewLicense(), }, previous: { - caption: "Previous", + caption: "%general.previous%", to: 0, }, next: { - caption: "I agree", + caption: "%general.iAgree%", suggested: true, action: () => this.licenseConfirmation(), }, }, { previous: { - caption: "Previous", + caption: "%general.previous%", to: 1, }, next: { - caption: "Continue", + caption: "%general.continue%", disabled: () => !this.identityInfoValid(), action: () => this.createAccount(), suggested: true, @@ -87,10 +87,10 @@ export class InitialSetupRuntime extends AppProcess { previous: { disabled: () => true, to: 3, - caption: "Previous", + caption: "%general.previous%", }, next: { - caption: "I clicked it", + caption: "%apps.initialSetupWizard.buttons.iClickedIt%", suggested: true, action: () => this.checkAccountActivation(), }, @@ -99,25 +99,25 @@ export class InitialSetupRuntime extends AppProcess { previous: { disabled: () => true, to: 4, - caption: "Previous", + caption: "%general.previous%", }, next: { - caption: "Let's begin", + caption: "%apps.initialSetupWizard.buttons.letsBegin%", action: () => this.finish(), suggested: true, }, }, { left: { - caption: "Cancel", + caption: "%general.cancel%", disabled: () => true, }, previous: { - caption: "Previous", + caption: "%general.previous%", disabled: () => true, }, next: { - caption: "Server's all good", + caption: "%apps.initialSetupWizard.serverAllGood%", to: 0, suggested: true, }, @@ -182,18 +182,17 @@ export class InitialSetupRuntime extends AppProcess { MessageBox( { - title: "Just making sure...", - message: - "By using ArcOS, you agree to the License Agreement. You may not violate any of the rules contained within this license. Continue?", + title: "%apps.initialSetupWizard.licenseConfirmation.title%", + message: "%apps.initialSetupWizard.licenseConfirmation.message%", buttons: [ { - caption: "Decline", + caption: "%general.decline%", action: () => { this.actionsDisabled.set(false); }, }, { - caption: "I agree", + caption: "%general.iAgree%", suggested: true, action: () => { this.pageNumber.set(this.pageNumber() + 1); @@ -213,19 +212,17 @@ export class InitialSetupRuntime extends AppProcess { MessageBox( { image: SecurityMediumIcon, - title: "ArcOS License - GPLv3", - message: `By using ArcOS, you agree to the GPLv3 License contained within: ${htmlspecialchars( - ArcLicense() - )}`, + title: "%apps.initialSetupWizard.viewLicense.title%", + message: `%apps.initialSetupWizard.viewLicense.message%: ${htmlspecialchars(ArcLicense())}`, buttons: [ { - caption: "Decline", + caption: "%general.decline%", action: () => { KernelStateHandler()?.loadState("licenseDeclined"); }, }, { - caption: "I agree", + caption: "%general.iAgree%", action: () => { this.actionsDisabled.set(false); }, @@ -253,11 +250,11 @@ export class InitialSetupRuntime extends AppProcess { MessageBox( { image: WarningIcon, - title: "You made a typo!", - message: "The passwords you entered don't match. Please re-enter them, and then try again.", + title: "%apps.initialSetupWizard.createAccount.passwordMismatch.title%", + message: "%apps.initialSetupWizard.createAccount.passwordMismatch.message%", buttons: [ { - caption: "Okay", + caption: "%general.okay%", suggested: true, action: () => { this.actionsDisabled.set(false); @@ -278,12 +275,11 @@ export class InitialSetupRuntime extends AppProcess { MessageBox( { image: ErrorIcon, - title: "Something went wrong", - message: - "An error occured while creating your account. We might be experiencing some technical difficulties, please try again later.", + title: "%apps.initialSetupWizard.createAccount.genericError.title%", + message: "%apps.initialSetupWizard.createAccount.genericError.message%", buttons: [ { - caption: "Okay", + caption: "%generic.okay%", suggested: true, action: () => { this.actionsDisabled.set(false); @@ -309,12 +305,11 @@ export class InitialSetupRuntime extends AppProcess { if (!token) { MessageBox( { - title: "Did you click the link?", - message: - "Our systems tell me that your account hasn't been activated yet. Are you sure you clicked the link? If you did, and you're still seeing this, please contact support.", + title: "%apps.initialSetupWizard.checkAccountActivationError.title%", + message: "%apps.initialSetupWizard.checkAccountActivationError.message%", buttons: [ { - caption: "Okay", + caption: "%generic.okay%", action: () => { this.actionsDisabled.set(false); }, diff --git a/src/apps/core/loginapp/ErrorMessage.svelte b/src/apps/core/loginapp/ErrorMessage.svelte index abdd3cbfd..9257c27a4 100755 --- a/src/apps/core/loginapp/ErrorMessage.svelte +++ b/src/apps/core/loginapp/ErrorMessage.svelte @@ -7,4 +7,4 @@

{@html $errorMessage}

- + diff --git a/src/apps/core/loginapp/LoginForm.svelte b/src/apps/core/loginapp/LoginForm.svelte index f3c756e8f..c0fe6589d 100755 --- a/src/apps/core/loginapp/LoginForm.svelte +++ b/src/apps/core/loginapp/LoginForm.svelte @@ -16,9 +16,9 @@ {#if $persistence} - + {:else if !serverInfo?.disableRegistration} - + {/if} diff --git a/src/apps/core/loginapp/runtime.ts b/src/apps/core/loginapp/runtime.ts index e17f97e41..3f7f15ac1 100755 --- a/src/apps/core/loginapp/runtime.ts +++ b/src/apps/core/loginapp/runtime.ts @@ -132,9 +132,9 @@ export class LoginAppRuntime extends AppProcess { getWelcomeString(): string { const hour = dayjs().hour(); - if (hour < 12) return "Good morning"; - if (hour < 18) return "Good afternoon"; - return "Good evening"; + if (hour < 12) return "%apps.loginApp.welcomeString.morning%"; + if (hour < 18) return "%apps.loginApp.welcomeString.afternoon%"; + return "%apps.loginApp.welcomeString.evening%"; } //#endregion @@ -157,22 +157,22 @@ export class LoginAppRuntime extends AppProcess { if (!userDaemon) { this.loadingStatus.set(""); - this.errorMessage.set("Failed to start user daemon"); + this.errorMessage.set("%apps.loginApp.errors.noDaemon%"); return; } - this.loadingStatus.set("Saving token"); + this.loadingStatus.set("%apps.loginApp.startDaemon.savingToken%"); this.saveToken(userDaemon); - this.loadingStatus.set("Loading your settings"); + this.loadingStatus.set("%apps.loginApp.startDaemon.loadingSettings%"); const userInfo = await userDaemon.getUserInfo(); if (!userInfo) { this.loadingStatus.set(""); - this.errorMessage.set("Failed to request user info"); + this.errorMessage.set("%apps.loginApp.errors.noUserInfo%"); return; } @@ -180,7 +180,7 @@ export class LoginAppRuntime extends AppProcess { this.profileImage.set(`${import.meta.env.DW_SERVER_URL}/user/pfp/${userInfo._id}${authcode()}`); if (userInfo.hasTotp && userInfo.restricted) { - this.loadingStatus.set("Requesting 2FA"); + this.loadingStatus.set("%apps.loginApp.startDaemon.request2fa%"); const unlocked = await this.askForTotp(token, userDaemon.userInfo?._id); if (!unlocked) { @@ -188,7 +188,7 @@ export class LoginAppRuntime extends AppProcess { await userDaemon.killSelf(); this.resetCookies(); this.loadingStatus.set(""); - this.errorMessage.set("You didn't enter a valid 2FA code!"); + this.errorMessage.set("%apps.loginApp.errors.totpInvalid%"); return; } } @@ -205,15 +205,15 @@ export class LoginAppRuntime extends AppProcess { this.savePersistence(username, this.profileImage()); - broadcast("Starting filesystem"); + broadcast("%apps.loginApp.startDaemon.startingFilesystem%"); await userDaemon.startFilesystemSupplier(); - broadcast("Starting synchronization"); + broadcast("%apps.loginApp.startDaemon.startingSync%"); await userDaemon.startPreferencesSync(); - broadcast("Reading profile customization"); + broadcast("%apps.loginApp.startDaemon.profileCustomization%"); this.profileName.set(userDaemon.preferences().account.displayName || username); if (!this.safeMode) { @@ -222,58 +222,58 @@ export class LoginAppRuntime extends AppProcess { this.savePersistence(username, this.profileImage(), this.loginBackground()); } - broadcast("Notifying login activity"); + broadcast("%apps.loginApp.startDaemon.notifyLoginActivity%"); await userDaemon.logActivity("login"); - broadcast("Starting service host"); + broadcast("%apps.loginApp.startDaemon.startServiceHost%"); await userDaemon.startServiceHost(async (serviceStep) => { if (serviceStep.id === "AppStorage") { - broadcast("Loading apps"); - await userDaemon.initAppStorage(userDaemon.appStorage()!, (app) => broadcast(`Loaded ${app.metadata.name}`)); + broadcast("%apps.loginApp.startDaemon.loadingApps%"); + await userDaemon.initAppStorage(userDaemon.appStorage()!, (app) => broadcast(app.metadata.name)); } else { - broadcast(`Started ${serviceStep.name}`); + broadcast(serviceStep.name); } }); - broadcast("Checking associations"); + broadcast("%apps.loginApp.startDaemon.checkingAssoc%"); await userDaemon.updateFileAssociations(); - broadcast("Connecting global dispatch"); + broadcast("%apps.loginApp.startDaemon.globalDispatch%"); await userDaemon.activateGlobalDispatch(); - broadcast("Welcome to ArcOS"); + broadcast("%apps.loginApp.startDaemon.welcome%"); if (!userDaemon.preferences().firstRunDone && !userDaemon.preferences().appPreferences.arcShell) { await this.firstRun(userDaemon); } - broadcast("Starting drive notifier watcher"); + broadcast("%apps.loginApp.startDaemon.driveNotifierWatcher%"); userDaemon.startDriveNotifierWatcher(); - broadcast("Starting share management"); + broadcast("%apps.loginApp.startDaemon.shareManagement%"); await userDaemon.startShareManager(); const storage = userDaemon.appStorage(); if (userDaemon.userInfo.admin) { - broadcast("Activating admin bootstrapper"); + broadcast("%apps.loginApp.startDaemon.adminBootstrapper%"); await userDaemon.activateAdminBootstrapper(); } else { await storage?.refresh(); } - broadcast("Starting status refresh"); + broadcast("%apps.loginApp.startDaemon.statusRefresh%"); await userDaemon.startSystemStatusRefresh(); - broadcast("Let's go!"); + broadcast("%apps.loginApp.startDaemon.letsGo%"); await KernelStateHandler()?.loadState("desktop", { userDaemon }); this.soundBus.playSound("arcos.system.logon"); userDaemon.setAppRendererClasses(userDaemon.preferences()); userDaemon.checkNightly(); - broadcast("Starting Workspaces"); + broadcast("%apps.loginApp.startDaemon.startingWorkspaces%"); await userDaemon.startVirtualDesktops(); - broadcast("Running autorun"); + broadcast("%apps.loginApp.startDaemon.autoload%"); await userDaemon.spawnAutoload(); await this.appStore()?.refresh(); @@ -296,7 +296,7 @@ export class LoginAppRuntime extends AppProcess { // this.hideProfileImage.set(true); this.type = "logoff"; - this.loadingStatus.set(`Goodbye, ${daemon.username}!`); + this.loadingStatus.set(`%apps.loginApp.exit.logoff% ${daemon.username}!`); this.errorMessage.set(""); for (const [_, proc] of [...KernelStack().store()]) { @@ -340,7 +340,7 @@ export class LoginAppRuntime extends AppProcess { this.profileName.set(daemon.preferences().account.displayName || daemon.username); } - this.loadingStatus.set(`Shutting down...`); + this.loadingStatus.set(`%apps.loginApp.exit.shutdown%`); this.errorMessage.set(""); await Sleep(2000); @@ -361,7 +361,7 @@ export class LoginAppRuntime extends AppProcess { this.profileName.set(daemon.preferences().account.displayName || daemon.username); } - this.loadingStatus.set(`Restarting...`); + this.loadingStatus.set(`%apps.loginApp.exit.restart%`); this.errorMessage.set(""); await Sleep(2000); @@ -376,13 +376,13 @@ export class LoginAppRuntime extends AppProcess { async proceed(username: string, password: string) { this.Log(`Trying login of '${username}'`); - this.loadingStatus.set(`Hi, ${username}!`); + this.loadingStatus.set(this.getWelcomeString()); const token = await LoginUser(username, password); if (!token) { this.loadingStatus.set(""); - this.errorMessage.set("Username or password incorrect."); + this.errorMessage.set("%apps.loginApp.errors.credentialIncorrect%"); return; } diff --git a/src/lang/en.json b/src/lang/en.json new file mode 100644 index 000000000..af584a032 --- /dev/null +++ b/src/lang/en.json @@ -0,0 +1,147 @@ +{ + "apps": { + "bootScreen": { + "pressAnyKey": "Press any key or click to start", + "safeMode": "Entering Safe Mode", + "arcTerm": "Starting ArcTerm" + }, + "initialSetupWizard": { + "_name": "Initial Setup Wizard", + "buttons": { + "viewLicense": "View License", + "iAgree": "I agree", + "iClickedIt": "I clicked it", + "letsBegin": "Let's begin", + "serverAllGood": "Server's all good" + }, + "licenseConfirmation": { + "title": "Just making sure...", + "message": "By using ArcOS, you agree to the License Agreement. You may not violate any of the rules contained within this license. Continue?" + }, + "viewLicense": { + "title": "ArcOS License - GPLv3", + "message": "By using ArcOS, you agree to the GPLv3 License contained within" + }, + "createAccount": { + "passwordMismatch": { + "title": "You made a typo!", + "message": "The passwords you entered don't match. Please re-enter them, and then try again." + }, + "genericError": { + "title": "Something went wrong", + "message": "An error occured while creating your account. We might be experiencing some technical difficulties, please try again later." + } + }, + "checkAccountActivationError": { + "title": "Did you click the link?", + "message": "Our systems tell me that your account hasn't been activated yet. Are you sure you clicked the link? If you did, and you're still seeing this, please contact support." + }, + "page": { + "checkInbox": { + "title": "Check your inbox", + "subtitle": "You've got mail!", + "message": "We sent you a link to activate your account. Open it to continue. Be sure to check your spam if you can’t find it." + }, + "finish": { + "title": "All finished!", + "subtitle": "You're all set.", + "message": "Your account has been set up successfully. Click Finish to start using ArcOS." + }, + "freshDeployment": { + "title": "Fresh deployment", + "message": "It appears this ReArc deployment is fresh. Before continuing, please make sure that that's supposed to be the case. If it is, click Next. The user you're about to create is considered user #0, and will be set up as a God Admin by default. The owner of this account is then automatically seen as the owner of the server. Also, please check the server configuration:", + "note": "Note: disableRegistration is ignored for the first user. This configuration option will be retained once the first user has been created." + }, + "identity": { + "title": "Your ArcOS Identity", + "subtitle": "We'll use this information to create your ArcOS account.", + "fields": { + "displayName": "Display Name", + "username": "Username", + "emailAddress": "Email address *", + "password": "Password", + "passwordConfirm": "Confirm" + }, + "errors": { + "usernameAndEmailTaken": "Username and email address are both taken!", + "usernameTaken": "Username is already taken!", + "emailTaken": "Email is already taken!", + "emailAndUsernameInvalid": "Username and email address are both invalid!", + "usernameInvalid": "Username is invalid!", + "emailInvalid": "Email address is invalid!", + "passwordtooWeak": "Password is too weak!", + "passwordweak": "Password is weak!", + "passwordmedium": "Password is medium!", + "passwordstrong": "Password is strong!" + }, + "disclaimer": "* You will receive an email with a link to activate your account. Your display name, username and password can be changed later on. To change your email, contact an administrator." + }, + "license": { + "title": "License Agreement", + "subtitle": "Scary stuff, I know", + "message": "By using ArcOS, you agree to the GPLv3 license." + }, + "welcome": { + "title": "Welcome", + "subtitle": "We're happy to have you", + "message": "Let's get you an ArcOS account. Click Next to get started, or Cancel to go back to the login screen." + } + } + }, + "loginApp": { + "welcomeString": { + "morning": "Good morning", + "afternoon": "Good afternoon", + "evening": "Good evening" + }, + "errors": { + "noDaemon": "Failed to start user daemon", + "noUserInfo": "Failed to request user info", + "totpInvalid": "You didn't enter a valid 2FA code!", + "credentialIncorrect": "Username or password incorrect." + }, + "startDaemon": { + "savingToken": "Saving token", + "loadingSettings": "Loading your settings", + "request2fa": "Requesting 2FA", + "startingFilesystem": "Starting filesystem", + "startingSync": "Starting synchronization", + "profileCustomization": "Reading profile customization", + "notifyLoginActivity": "Notifying login activity", + "startServiceHost": "Starting service host", + "loadingApps": "Loading apps... ", + "checkingAssoc": "Checking associations", + "globalDispatch": "Connecting global dispatch", + "welcome": "Welcome to ArcOS", + "driveNotifierWatcher": "Starting drive notifier watcher", + "shareManagement": "Starting share management", + "adminBootstrapper": "Activating admin bootstrapper", + "statusRefresh": "Starting status refresh", + "letsGo": "Let's go!", + "startingWorkspaces": "Starting workspaces", + "autoload": "Spawning autorun" + }, + "exit": { + "logoff": "Goodbye, ", + "shutdown": "Shutting down...", + "restart": "Restarting..." + }, + "loginForm": { + "switchUser": "Switch user", + "noAccount": "No account?", + "username": "Username", + "password": "Password" + } + } + }, + "general": { + "betaPill": "BETA", + "cancel": "Cancel", + "okay": "Okay", + "previous": "Previous", + "next": "Next", + "continue": "Continue", + "decline": "Decline", + "iAgree": "I agree" + } +} diff --git a/src/lang/nl.json b/src/lang/nl.json new file mode 100644 index 000000000..8b9109976 --- /dev/null +++ b/src/lang/nl.json @@ -0,0 +1,147 @@ +{ + "apps": { + "bootScreen": { + "pressAnyKey": "Druk op een toets of klik om te starten", + "safeMode": "Veilige modus wordt geladen", + "arcTerm": "ArcTerm wordt gestart" + }, + "initialSetupWizard": { + "_name": "Wizard voor eerste installatie", + "buttons": { + "viewLicense": "Licentie openen", + "iAgree": "Ik ga akkoord", + "iClickedIt": "Ik heb geklikt", + "letsBegin": "Laten we beginnen", + "serverAllGood": "Server is in orde" + }, + "licenseConfirmation": { + "title": "Even voor de zekerheid...", + "message": "Door ArcOS te gebruiken, ga je akkoord met de licentieovereenkomst. Je mag geen van de regels in deze licentie overtreden. Doorgaan?" + }, + "viewLicense": { + "title": "ArcOS Licentie - GPLv3", + "message": "Door ArcOS te gebruiken, ga je akkoord met de GPLv3 licentie die hierin is opgenomen" + }, + "createAccount": { + "passwordMismatch": { + "title": "Je hebt een typfout gemaakt!", + "message": "De ingevoerde wachtwoorden komen niet overeen. Voer ze opnieuw in en probeer het dan opnieuw." + }, + "genericError": { + "title": "Er is iets misgegaan", + "message": "Er is een fout opgetreden bij het aanmaken van je account. Mogelijk ondervinden we technische problemen, probeer het later opnieuw." + } + }, + "checkAccountActivationError": { + "title": "Heb je op de link geklikt?", + "message": "Onze systemen vertellen me dat je account nog niet is geactiveerd. Weet je zeker dat je op de link hebt geklikt? Als je dat hebt gedaan en je ziet dit nog steeds, neem dan contact op met de ondersteuning." + }, + "page": { + "checkInbox": { + "title": "Controleer je inbox", + "subtitle": "Je hebt post!", + "message": "We hebben je een link gestuurd om je account te activeren. Open deze om verder te gaan. Controleer je spammap als je hem niet kunt vinden." + }, + "finish": { + "title": "Klaar!", + "subtitle": "Alles is ingesteld.", + "message": "Je account is succesvol ingesteld. Klik op Voltooien om ArcOS te gaan gebruiken." + }, + "freshDeployment": { + "title": "Nieuwe implementatie", + "message": "Het lijkt erop dat deze ReArc implementatie nieuw is. Controleer voordat je verder gaat of dit de bedoeling is. Als dit het geval is, klik dan op Volgende. De gebruiker die je nu gaat aanmaken wordt beschouwd als gebruiker #0 en wordt standaard ingesteld als een God Admin. De eigenaar van dit account wordt dan automatisch gezien als de eigenaar van de server. Controleer ook de serverconfiguratie:", + "note": "Opmerking: disableRegistration wordt genegeerd voor de eerste gebruiker. Deze configuratieoptie blijft behouden zodra de eerste gebruiker is aangemaakt." + }, + "identity": { + "title": "Je ArcOS Identiteit", + "subtitle": "We gebruiken deze informatie om je ArcOS account aan te maken.", + "fields": { + "displayName": "Weergavenaam", + "username": "Gebruikersnaam", + "emailAddress": "E-mailadres *", + "password": "Wachtwoord", + "passwordConfirm": "Bevestigen" + }, + "errors": { + "usernameAndEmailTaken": "Gebruikersnaam en e-mailadres zijn beide al in gebruik!", + "usernameTaken": "Gebruikersnaam is al in gebruik!", + "emailTaken": "E-mailadres is al in gebruik!", + "emailAndUsernameInvalid": "Gebruikersnaam en e-mailadres zijn beide ongeldig!", + "usernameInvalid": "Gebruikersnaam is ongeldig!", + "emailInvalid": "E-mailadres is ongeldig!", + "passwordtooWeak": "Wachtwoord is te zwak!", + "passwordweak": "Wachtwoord is zwak!", + "passwordmedium": "Wachtwoord is gemiddeld!", + "passwordstrong": "Wachtwoord is sterk!" + }, + "disclaimer": "* Je ontvangt een e-mail met een link om je account te activeren. Je weergavenaam, gebruikersnaam en wachtwoord kunnen later worden gewijzigd. Neem contact op met een beheerder om je e-mailadres te wijzigen." + }, + "license": { + "title": "Licentieovereenkomst", + "subtitle": "Eng spul, ik weet het", + "message": "Door ArcOS te gebruiken, ga je akkoord met de GPLv3 licentie." + }, + "welcome": { + "title": "Welkom", + "subtitle": "Wat fijn om kennis te maken.", + "message": "Laten we een ArcOS account voor je aanmaken. Klik op Volgende om te beginnen, of op Annuleren om terug te gaan naar het inlogscherm." + } + } + }, + "loginApp": { + "welcomeString": { + "morning": "Goedemorgen", + "afternoon": "Goedemiddag", + "evening": "Goedenavond" + }, + "errors": { + "noDaemon": "Kan daemon niet starten", + "noUserInfo": "Kan gebruikersinformatie niet opvragen", + "totpInvalid": "Je hebt geen geldige 2FA-code ingevoerd!", + "credentialIncorrect": "Gebruikersnaam of wachtwoord onjuist." + }, + "startDaemon": { + "savingToken": "Inlog opslaan", + "loadingSettings": "Persoonlijke gegevens opvragen", + "request2fa": "2FA code vragen", + "startingFilesystem": "Bestandssysteem starten", + "startingSync": "Synchronisatie starten", + "profileCustomization": "Profiel instellingen lezen", + "notifyLoginActivity": "Inlogactiviteit melden", + "startServiceHost": "Servicehost starten", + "loadingApps": "Apps laden... ", + "checkingAssoc": "Associaties controleren", + "globalDispatch": "Global dispatch verbinden", + "welcome": "Welkom bij ArcOS", + "driveNotifierWatcher": "Drive notifier watcher starten", + "shareManagement": "Delenbeheer starten", + "adminBootstrapper": "Admin bootstrapper activeren", + "statusRefresh": "Statusvernieuwing starten", + "letsGo": "Laten we gaan!", + "startingWorkspaces": "Werkruimtes starten", + "autoload": "Autorun uitvoeren" + }, + "exit": { + "logoff": "Tot ziens, ", + "shutdown": "Afsluiten...", + "restart": "Opnieuw opstarten..." + }, + "loginForm": { + "switchUser": "Gebruiker wisselen", + "noAccount": "Geen account?", + "username": "Gebruikersnaam", + "password": "Wachtwoord" + } + } + }, + "general": { + "betaPill": "BETA", + "cancel": "Annuleren", + "okay": "Oké", + "previous": "Vorige", + "next": "Volgende", + "continue": "Doorgaan", + "decline": "Afwijzen", + "iAgree": "Ik ga akkoord" + } +} diff --git a/src/ts/kernel/init.ts b/src/ts/kernel/init.ts index 63fb6c851..40c7aec32 100755 --- a/src/ts/kernel/init.ts +++ b/src/ts/kernel/init.ts @@ -8,6 +8,7 @@ import { textToBlob } from "$ts/util/convert"; import type { ServerManagerType } from "$types/kernel"; import { Process } from "../process/instance"; import { StateHandler } from "../state"; +import { I18n } from "./mods/i18n"; export class InitProcess extends Process { //#region LIFECYCLE @@ -28,6 +29,7 @@ export class InitProcess extends Process { await KernelStack().startRenderer(this.pid); const server = getKMod("server"); + const i18n = getKMod("i18n"); const connected = server.connected; const state = await KernelStack().spawn(StateHandler, undefined, "SYSTEM", this.pid, "ArcOS", States); const kernel = Kernel(); @@ -41,6 +43,8 @@ export class InitProcess extends Process { __Console__.timeEnd("** Init jumpstart"); this.name = "InitProcess"; + i18n.startObserver(KernelStack().renderer?.target!); + if (ArcMode() === "nightly") this.nightly(); this.setSource(__SOURCE__); diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts new file mode 100644 index 000000000..d10595f8a --- /dev/null +++ b/src/ts/kernel/mods/i18n/index.ts @@ -0,0 +1,167 @@ +import EnglishLanguage from "$lang/en.json"; +import DutchLanguage from "$lang/nl.json"; +import { getKMod } from "$ts/env"; +import { getJsonHierarchy } from "$ts/hierarchy"; +import { KernelModule } from "$ts/kernel/module"; +import { detectJavaScript } from "$ts/util"; +import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; + +export class I18n extends KernelModule { + language: string = "en"; + dispatch: SystemDispatchType; + observer: MutationObserver | null = null; + TARGET?: HTMLDivElement; + + // Attributes to translate + TRANSLATABLE_ATTRIBUTES = ["placeholder", "title", "alt", "aria-label", "data-tooltip"]; + + LANGUAGES: Record = { + en: EnglishLanguage, + nl: DutchLanguage, + }; + + constructor(kernel: ConstructedWaveKernel, id: string) { + super(kernel, id); + this.dispatch = getKMod("dispatch"); + } + + setLanguage(lang: string) { + this.language = lang; + this.rerunObserver(); + this.dispatch.dispatch("i18n-lang-change", [lang]); + } + + rerunObserver() { + if (!this.TARGET) return; + + // Handle elements with data-i18n + this.TARGET.querySelectorAll("[data-i18n]").forEach((el) => { + const key = el.getAttribute("data-i18n"); + if (!key) return; + const translated = this.translateToken(key, el); + + if (detectJavaScript(translated)?.length) return; + el.innerHTML = translated; + }); + + // Handle attribute translations + this.translateAttributes(this.TARGET); + } + + translateToken(token: string, contextEl?: HTMLElement): string { + if (token.startsWith("lang=")) { + this.setLanguage(token.replace("lang=", "")); + return this.language; + } + + // Look for nearest parent with data-prefix + if (contextEl) { + const prefixHolder = contextEl.closest("[data-prefix]"); + if (prefixHolder) { + const prefix = prefixHolder.getAttribute("data-prefix"); + if (prefix && !token.startsWith(prefix)) { + token = `${prefix}.${token}`; + } + } + } + + const dict = this.LANGUAGES[this.language] || {}; + const translated = getJsonHierarchy(dict, token); + + if (translated === undefined || translated === null) { + console.warn(`[i18n] Missing translation for token "${token}"`); + return `%${token}%`; + } + + return translated; + } + + translateAttributes(node: HTMLElement) { + // Process all elements within the node + const elements = [node, ...Array.from(node.querySelectorAll("*"))]; + + elements.forEach((el) => { + this.TRANSLATABLE_ATTRIBUTES.forEach((attr) => { + const value = el.getAttribute(attr); + if (value) { + const match = value.match(/%([\w.=\-]+)%/); + if (match) { + const key = match[1]; + const translated = this.translateToken(key, el); + + if (!detectJavaScript(translated)?.length) { + el.setAttribute(attr, value.replace(/%([\w.=\-]+)%/, translated)); + } + } + } + }); + }); + } + + replaceInNode(node: Node) { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.nodeValue ?? ""; + + // skip text already inside a data-i18n span + if (node.parentElement?.hasAttribute("data-i18n")) return; + + const match = text.match(/%([\w.=\-]+)%/); + if (match) { + const key = match[1]; + const parent = node.parentNode; + if (!parent) return; + + // Remove ANY existing translation spans in the same parent + // This handles the case where the translation ID changes (e.g., store updates) + const existingSpans = parent.querySelectorAll("span[data-i18n]"); + existingSpans.forEach((span) => { + if (span.parentNode === parent) { + parent.removeChild(span); + } + }); + + const span = document.createElement("span"); + span.setAttribute("data-i18n", key); + const translated = this.translateToken(key, node.parentElement!); + + if (detectJavaScript(translated)?.length) return; + span.innerHTML = translated; + + parent.replaceChild(span, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as HTMLElement; + + if (element.hasAttribute("data-i18n")) return; + + // Translate attributes for this element + this.translateAttributes(element); + + // Continue with child nodes (convert to array to avoid live NodeList issues) + Array.from(node.childNodes).forEach((n) => this.replaceInNode(n)); + } + } + + startObserver(target: HTMLDivElement) { + if (!target) throw new Error(`I18n: target does not exist`); + + this.TARGET = target; + this.observer = new MutationObserver((mutations) => { + for (const m of mutations) { + m.addedNodes.forEach((n) => this.replaceInNode(n)); + } + }); + + this.observer.observe(this.TARGET, { + childList: true, + subtree: true, + }); + + this.replaceInNode(this.TARGET); + } + + stopObserver() { + this.observer?.disconnect(); + this.observer = null; + } +} diff --git a/src/ts/kernel/module/store.ts b/src/ts/kernel/module/store.ts index c3af08ba7..38b64e23b 100755 --- a/src/ts/kernel/module/store.ts +++ b/src/ts/kernel/module/store.ts @@ -3,12 +3,14 @@ import { SystemDispatch } from "$ts/kernel/mods/dispatch"; import { Filesystem } from "$ts/kernel/mods/fs"; import { SoundBus } from "$ts/kernel/mods/soundbus"; import { Environment } from "../mods/env"; +import { I18n } from "../mods/i18n"; import { ServerManager } from "../mods/server"; import { ProcessHandler } from "../mods/stack"; export const KernelModules: Record = { env: Environment, dispatch: SystemDispatch, + i18n: I18n, soundbus: SoundBus, stack: ProcessHandler, server: ServerManager, diff --git a/tsconfig.app.json b/tsconfig.app.json index dd0c2f749..3e1f935f3 100755 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -22,7 +22,8 @@ "$css/*": ["./src/css/*"], "$types/*": ["./src/types/*"], "$lib/*": ["./src/lib/*"], - "$ts/*": ["./src/ts/*"] + "$ts/*": ["./src/ts/*"], + "$lang/*": ["./src/lang/*"] }, "esModuleInterop": true }, diff --git a/vite.config.ts b/vite.config.ts index ad17bd7fa..84553cc8b 100755 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ $types: resolve(__dirname, "./src/types"), $ts: resolve(__dirname, "./src/ts"), $lib: resolve(__dirname, "./src/lib"), + $lang: resolve(__dirname, "./src/lang"), }, }, build: { From d6e59b97e5cfe7808be456b04004e1c4722203c6 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Sat, 27 Sep 2025 22:27:12 +0200 Subject: [PATCH 02/17] translation: AcceleratorOverview --- .../acceleratoroverview/AcceleratorOverview.svelte | 6 +++--- src/apps/core/loginapp/runtime.ts | 2 +- src/lang/en.json | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/apps/components/acceleratoroverview/AcceleratorOverview.svelte b/src/apps/components/acceleratoroverview/AcceleratorOverview.svelte index 173b0d3df..a3e966f7d 100755 --- a/src/apps/components/acceleratoroverview/AcceleratorOverview.svelte +++ b/src/apps/components/acceleratoroverview/AcceleratorOverview.svelte @@ -6,9 +6,9 @@ {#if $store && $apps} -
-

Keyboard Shortcuts

-

Get more work done faster with these handy shortcuts!

+
+

%title%

+

%subtitle%

{#each $store as [name, shortcuts]} diff --git a/src/apps/core/loginapp/runtime.ts b/src/apps/core/loginapp/runtime.ts index 38009b169..1db2e57ff 100755 --- a/src/apps/core/loginapp/runtime.ts +++ b/src/apps/core/loginapp/runtime.ts @@ -132,7 +132,7 @@ export class LoginAppRuntime extends AppProcess { getWelcomeString(): string { const hour = dayjs().hour(); - if (hour < 6) return "Hi, go to sleep"; + if (hour < 6) return "%apps.loginApp.welcomeString.night%"; if (hour < 12) return "%apps.loginApp.welcomeString.morning%"; if (hour < 18) return "%apps.loginApp.welcomeString.afternoon%"; return "%apps.loginApp.welcomeString.evening%"; diff --git a/src/lang/en.json b/src/lang/en.json index af584a032..16e9c1a40 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -90,6 +90,7 @@ }, "loginApp": { "welcomeString": { + "night": "Hi, go to sleep", "morning": "Good morning", "afternoon": "Good afternoon", "evening": "Good evening" @@ -132,6 +133,12 @@ "username": "Username", "password": "Password" } + }, + "AcceleratorOverview": { + "header": { + "title": "Keyboard shortcuts", + "subtitle": "Get more work done faster with these handy shortcuts!" + } } }, "general": { From 3d3bc8af5996ef1b20e5f31cb40d948257d58a5c Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Sat, 27 Sep 2025 23:46:22 +0200 Subject: [PATCH 03/17] fuck --- src/apps/components/appinfo/runtime.ts | 6 +- src/lang/en.json | 9 ++ src/ts/kernel/mods/i18n/index.ts | 128 +++++-------------------- 3 files changed, 38 insertions(+), 105 deletions(-) diff --git a/src/apps/components/appinfo/runtime.ts b/src/apps/components/appinfo/runtime.ts index 0d38c851e..79653496e 100755 --- a/src/apps/components/appinfo/runtime.ts +++ b/src/apps/components/appinfo/runtime.ts @@ -26,8 +26,8 @@ export class AppInfoRuntime extends AppProcess { if (!targetApp) { this.userDaemon?.sendNotification({ - title: "App not found", - message: `AppInfo couldn't find any information about "${this.targetAppId}". Is it installed?`, + title: "%apps.AppInfo.noTargetApp.title%", + message: `%apps.AppInfo.noTargetApp.message(${this.targetAppId})%`, image: "AppInfoIcon", timeout: 6000, }); @@ -44,7 +44,7 @@ export class AppInfoRuntime extends AppProcess { async killAll() { const elevated = await this.userDaemon?.manuallyElevate({ - what: `ArcOS needs your permission to kill all instances of an app`, + what: `%apps.AppInfo.killAll.what%`, image: this.userDaemon?.getAppIcon(this.targetApp()) || this.getIconCached("ComponentIcon"), title: this.targetApp().metadata.name, description: this.targetAppId, diff --git a/src/lang/en.json b/src/lang/en.json index 16e9c1a40..5bfeb9e35 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -139,6 +139,15 @@ "title": "Keyboard shortcuts", "subtitle": "Get more work done faster with these handy shortcuts!" } + }, + "AppInfo": { + "noTargetApp": { + "title": "App not found", + "message": "AppInfo couldn't find any information about \"{{0}}\". Is it installed?" + }, + "killAll": { + "what": "ArcOS needs your permission to kill all instances of an app" + } } }, "general": { diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index d10595f8a..8828376b3 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -1,12 +1,13 @@ import EnglishLanguage from "$lang/en.json"; import DutchLanguage from "$lang/nl.json"; -import { getKMod } from "$ts/env"; +import { getKMod, KernelStack } from "$ts/env"; import { getJsonHierarchy } from "$ts/hierarchy"; import { KernelModule } from "$ts/kernel/module"; import { detectJavaScript } from "$ts/util"; import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; export class I18n extends KernelModule { + REGEX = /%(?[\w.=\-]+)(?:\((?(.*?))\)|)%/gm; language: string = "en"; dispatch: SystemDispatchType; observer: MutationObserver | null = null; @@ -25,121 +26,43 @@ export class I18n extends KernelModule { this.dispatch = getKMod("dispatch"); } - setLanguage(lang: string) { - this.language = lang; - this.rerunObserver(); - this.dispatch.dispatch("i18n-lang-change", [lang]); + async _init(): Promise { + this.startObserver(document.querySelector("#appRenderer")!); } - rerunObserver() { - if (!this.TARGET) return; + replaceInNode(node: Node) { + if (node.nodeType !== node.TEXT_NODE) return; - // Handle elements with data-i18n - this.TARGET.querySelectorAll("[data-i18n]").forEach((el) => { - const key = el.getAttribute("data-i18n"); - if (!key) return; - const translated = this.translateToken(key, el); + const str = node.textContent ?? ""; + const results = [...str.matchAll(this.REGEX)].map(({ groups }) => ({ + id: groups?.id, + inlays: groups?.inlays ? groups.inlays.split(",") : [], + })); - if (detectJavaScript(translated)?.length) return; - el.innerHTML = translated; - }); + if (!results.length) return; - // Handle attribute translations - this.translateAttributes(this.TARGET); - } + let resultString = ""; + for (const result of results) { + const value = getJsonHierarchy(this.LANGUAGES[this.language], result.id ?? ""); - translateToken(token: string, contextEl?: HTMLElement): string { - if (token.startsWith("lang=")) { - this.setLanguage(token.replace("lang=", "")); - return this.language; - } + if (!result.id || !value) { + resultString += `%${result.id}(${result.inlays?.join(",") ?? ""})%`; - // Look for nearest parent with data-prefix - if (contextEl) { - const prefixHolder = contextEl.closest("[data-prefix]"); - if (prefixHolder) { - const prefix = prefixHolder.getAttribute("data-prefix"); - if (prefix && !token.startsWith(prefix)) { - token = `${prefix}.${token}`; - } + continue; } - } - - const dict = this.LANGUAGES[this.language] || {}; - const translated = getJsonHierarchy(dict, token); - if (translated === undefined || translated === null) { - console.warn(`[i18n] Missing translation for token "${token}"`); - return `%${token}%`; - } + let partialResultString = value as string; - return translated; - } - - translateAttributes(node: HTMLElement) { - // Process all elements within the node - const elements = [node, ...Array.from(node.querySelectorAll("*"))]; - - elements.forEach((el) => { - this.TRANSLATABLE_ATTRIBUTES.forEach((attr) => { - const value = el.getAttribute(attr); - if (value) { - const match = value.match(/%([\w.=\-]+)%/); - if (match) { - const key = match[1]; - const translated = this.translateToken(key, el); - - if (!detectJavaScript(translated)?.length) { - el.setAttribute(attr, value.replace(/%([\w.=\-]+)%/, translated)); - } - } + if (result.inlays) { + for (let i = 0; i < result.inlays.length; i++) { + partialResultString = partialResultString.replace(`{{${i}}}`, result.inlays[i]); } - }); - }); - } - - replaceInNode(node: Node) { - if (node.nodeType === Node.TEXT_NODE) { - const text = node.nodeValue ?? ""; - - // skip text already inside a data-i18n span - if (node.parentElement?.hasAttribute("data-i18n")) return; - - const match = text.match(/%([\w.=\-]+)%/); - if (match) { - const key = match[1]; - const parent = node.parentNode; - if (!parent) return; - - // Remove ANY existing translation spans in the same parent - // This handles the case where the translation ID changes (e.g., store updates) - const existingSpans = parent.querySelectorAll("span[data-i18n]"); - existingSpans.forEach((span) => { - if (span.parentNode === parent) { - parent.removeChild(span); - } - }); - - const span = document.createElement("span"); - span.setAttribute("data-i18n", key); - const translated = this.translateToken(key, node.parentElement!); - - if (detectJavaScript(translated)?.length) return; - span.innerHTML = translated; - - parent.replaceChild(span, node); } - } else if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as HTMLElement; - - if (element.hasAttribute("data-i18n")) return; - // Translate attributes for this element - this.translateAttributes(element); - - // Continue with child nodes (convert to array to avoid live NodeList issues) - Array.from(node.childNodes).forEach((n) => this.replaceInNode(n)); + resultString += partialResultString; } + + console.warn(str, resultString); } startObserver(target: HTMLDivElement) { @@ -155,6 +78,7 @@ export class I18n extends KernelModule { this.observer.observe(this.TARGET, { childList: true, subtree: true, + attributes: true, }); this.replaceInNode(this.TARGET); From 044fea9fa6c063def7f80ff9bd4e0cb2b09ed3d4 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Sun, 28 Sep 2025 23:19:15 +0200 Subject: [PATCH 04/17] you mother- --- src/apps/components/appinfo/runtime.ts | 4 +- src/apps/core/loginapp/Loading.svelte | 2 +- src/apps/core/loginapp/runtime.ts | 2 + src/ts/kernel/mods/i18n/index.ts | 59 ++++++++++++++++---------- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/apps/components/appinfo/runtime.ts b/src/apps/components/appinfo/runtime.ts index 79653496e..8fa30c571 100755 --- a/src/apps/components/appinfo/runtime.ts +++ b/src/apps/components/appinfo/runtime.ts @@ -26,8 +26,8 @@ export class AppInfoRuntime extends AppProcess { if (!targetApp) { this.userDaemon?.sendNotification({ - title: "%apps.AppInfo.noTargetApp.title%", - message: `%apps.AppInfo.noTargetApp.message(${this.targetAppId})%`, + title: "%apps.AppInfo.noTargetApp.title% --- %general.okay%", + message: `%apps.AppInfo.noTargetApp.message(${this.targetAppId})% --- %general.iAgree%. %general.okay%%general.okay%`, image: "AppInfoIcon", timeout: 6000, }); diff --git a/src/apps/core/loginapp/Loading.svelte b/src/apps/core/loginapp/Loading.svelte index 9ce31d65a..c767498eb 100755 --- a/src/apps/core/loginapp/Loading.svelte +++ b/src/apps/core/loginapp/Loading.svelte @@ -7,5 +7,5 @@

- {@html $loadingStatus} + {$loadingStatus}

diff --git a/src/apps/core/loginapp/runtime.ts b/src/apps/core/loginapp/runtime.ts index 1db2e57ff..8ca3bd80a 100755 --- a/src/apps/core/loginapp/runtime.ts +++ b/src/apps/core/loginapp/runtime.ts @@ -199,7 +199,9 @@ export class LoginAppRuntime extends AppProcess { const verbose = userDaemon.preferences().enableVerboseLogin; const broadcast = (message: string) => { if (!verbose) return; + console.warn("BROADCAST", message); this.loadingStatus.set(message); + console.warn("END BROADCAST", message); }; this.loadPersistence(); diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index 8828376b3..2a029e1ba 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -30,39 +30,49 @@ export class I18n extends KernelModule { this.startObserver(document.querySelector("#appRenderer")!); } - replaceInNode(node: Node) { - if (node.nodeType !== node.TEXT_NODE) return; + replaceInNode(node: Node): void { + if (node.nodeType === node.TEXT_NODE) { + const str = node.textContent ?? ""; + const results = [...str.matchAll(this.REGEX)].map(({ groups, [0]: key }) => ({ + id: groups?.id, + inlays: groups?.inlays ? groups.inlays.split(",") : [], + key, + })); + + if (!results.length) { + return; + } - const str = node.textContent ?? ""; - const results = [...str.matchAll(this.REGEX)].map(({ groups }) => ({ - id: groups?.id, - inlays: groups?.inlays ? groups.inlays.split(",") : [], - })); + let resultString = str; + for (const result of results) { + const value = getJsonHierarchy(this.LANGUAGES[this.language], result.id ?? ""); - if (!results.length) return; + if (!result.id || !value) { + resultString = resultString.replace(result.key, "??"); - let resultString = ""; - for (const result of results) { - const value = getJsonHierarchy(this.LANGUAGES[this.language], result.id ?? ""); + continue; + } - if (!result.id || !value) { - resultString += `%${result.id}(${result.inlays?.join(",") ?? ""})%`; + let partialResultString = value as string; - continue; - } + if (result.inlays) { + for (let i = 0; i < result.inlays.length; i++) { + partialResultString = partialResultString.replace(`{{${i}}}`, result.inlays[i]); + } + } - let partialResultString = value as string; + resultString = resultString.replace(result.key, partialResultString); + } - if (result.inlays) { - for (let i = 0; i < result.inlays.length; i++) { - partialResultString = partialResultString.replace(`{{${i}}}`, result.inlays[i]); - } + if (node.parentElement) { + node.parentElement.setAttribute(`data-i18n-original`, str); + node.parentElement.textContent = resultString; } - resultString += partialResultString; + console.warn(str, resultString); + } else if (node.nodeType === node.ELEMENT_NODE) { + Array.from(node.childNodes).forEach((n) => this.replaceInNode(n)); } - - console.warn(str, resultString); } startObserver(target: HTMLDivElement) { @@ -79,6 +89,9 @@ export class I18n extends KernelModule { childList: true, subtree: true, attributes: true, + characterData: true, + characterDataOldValue: true, + attributeOldValue: true, }); this.replaceInNode(this.TARGET); From 3da3a090268260cb7410ab39e1de106ef0cb13d9 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 20:22:23 +0200 Subject: [PATCH 05/17] Finish translation engine and AppInfo i18n --- src/apps/components/appinfo/AppInfo.ts | 2 +- .../components/appinfo/AppInfo/Actions.svelte | 4 +- .../components/appinfo/AppInfo/Header.svelte | 26 ++- .../appinfo/AppInfo/IndepthInfo.svelte | 32 ++-- .../appinfo/AppInfo/InternalInfo.svelte | 8 +- .../appinfo/AppInfo/ProcessInfo.svelte | 10 +- .../appinfo/AppInfo/ThirdPartyInfo.svelte | 14 +- src/apps/components/appinfo/runtime.ts | 5 +- src/apps/core/bootscreen/bootScreen.ts | 2 +- src/apps/core/loginapp/loginApp.ts | 2 +- src/apps/core/loginapp/runtime.ts | 12 +- .../user/appstore/Pages/RecentlyAdded.svelte | 2 - src/lang/en.json | 69 +++++++- src/lang/nl.json | 148 +----------------- src/ts/apps/store.ts | 8 +- src/ts/kernel/mods/i18n/index.ts | 85 ++++++---- src/ts/writable.ts | 18 ++- 17 files changed, 207 insertions(+), 240 deletions(-) diff --git a/src/apps/components/appinfo/AppInfo.ts b/src/apps/components/appinfo/AppInfo.ts index 335a73a8f..94a83fceb 100644 --- a/src/apps/components/appinfo/AppInfo.ts +++ b/src/apps/components/appinfo/AppInfo.ts @@ -5,7 +5,7 @@ import { AppInfoRuntime } from "./runtime"; const AppInfoApp: App = { metadata: { - name: "App Info", + name: "%apps.AppInfo._name%", author: "Izaak Kuipers", version: "3.0.1", icon: "AppInfoIcon", diff --git a/src/apps/components/appinfo/AppInfo/Actions.svelte b/src/apps/components/appinfo/AppInfo/Actions.svelte index b66bfad9b..b611d4353 100755 --- a/src/apps/components/appinfo/AppInfo/Actions.svelte +++ b/src/apps/components/appinfo/AppInfo/Actions.svelte @@ -21,7 +21,7 @@

{appId}

- - + +
diff --git a/src/apps/components/appinfo/AppInfo/Header.svelte b/src/apps/components/appinfo/AppInfo/Header.svelte index f7f6ebeba..6f11e317c 100755 --- a/src/apps/components/appinfo/AppInfo/Header.svelte +++ b/src/apps/components/appinfo/AppInfo/Header.svelte @@ -45,26 +45,36 @@

- {target?.metadata?.name || "Unknown"} + {target?.metadata?.name || "%general.unknown%"} {#if disabled} {/if}

-

{target?.metadata?.author || "No author"}

+

{target?.metadata?.author || "%general.noAuthor%"}

- + {#if (target?.entrypoint || target?.workingDirectory) && installed} - + {/if} - +
diff --git a/src/apps/components/appinfo/AppInfo/IndepthInfo.svelte b/src/apps/components/appinfo/AppInfo/IndepthInfo.svelte index 727ac884a..1dded77f6 100755 --- a/src/apps/components/appinfo/AppInfo/IndepthInfo.svelte +++ b/src/apps/components/appinfo/AppInfo/IndepthInfo.svelte @@ -11,43 +11,45 @@ - + {Math.max(0, target.size?.w || 0) || "F"} x {Math.max(0, target.size?.h || 0) || "F"} - + {Math.max(0, target.minSize?.w || 0) || "F"} x {Math.max(0, target.minSize?.h || 0) || "F"} - + {Math.max(0, target.maxSize?.w || 0) || "F"} x {Math.max(0, target.maxSize?.h || 0) || "F"} - +
- - - + + +
- + {#if target?.position?.centered} - Centered + %indepthInfo.centered% {:else if target?.position?.x || target.position?.y} {target?.position?.x}, {target?.position?.y} {:else} - Corner of screen + %indepthInfo.cornerOfScreen% {/if} - + {AppOrigins[target?.originId || "injected"]} - - {target?.core ? "Yes" : "No"} + + {target?.core ? "%general.yes%" : "%general.no%"} - - {target?.hidden ? "Yes" : "No"} + + {target?.hidden ? "%general.yes%" : "%general.no%"}
diff --git a/src/apps/components/appinfo/AppInfo/InternalInfo.svelte b/src/apps/components/appinfo/AppInfo/InternalInfo.svelte index e9361e6ff..71ad9bcb3 100644 --- a/src/apps/components/appinfo/AppInfo/InternalInfo.svelte +++ b/src/apps/components/appinfo/AppInfo/InternalInfo.svelte @@ -10,18 +10,18 @@ - + {target._internalLoadTime?.toFixed(2) || 0}ms - + {target._internalSysVer} - + {target._internalMinVer || "-"} - + {target._internalOriginalPath} diff --git a/src/apps/components/appinfo/AppInfo/ProcessInfo.svelte b/src/apps/components/appinfo/AppInfo/ProcessInfo.svelte index 68812697d..ba306d2aa 100755 --- a/src/apps/components/appinfo/AppInfo/ProcessInfo.svelte +++ b/src/apps/components/appinfo/AppInfo/ProcessInfo.svelte @@ -31,12 +31,12 @@ - - {count} instance(s) + + %processInfo.instances({count})% - - {pid < 0 ? "None" : pid} + + {pid < 0 ? "%general.none%" : pid} - + diff --git a/src/apps/components/appinfo/AppInfo/ThirdPartyInfo.svelte b/src/apps/components/appinfo/AppInfo/ThirdPartyInfo.svelte index 19393bacc..4159c96f8 100755 --- a/src/apps/components/appinfo/AppInfo/ThirdPartyInfo.svelte +++ b/src/apps/components/appinfo/AppInfo/ThirdPartyInfo.svelte @@ -10,19 +10,19 @@ - Yes - - {target.fileSignatures ? Object.entries(target.fileSignatures).length : "None"} + %general.yes% + + {target.fileSignatures ? Object.entries(target.fileSignatures).length : "%general.none%"} - - {target.process ? "Yes" : "No"} + + {target.process ? "%general.yes%" : "%general.no%"} - + {target.workingDirectory} - + {target.entrypoint} diff --git a/src/apps/components/appinfo/runtime.ts b/src/apps/components/appinfo/runtime.ts index 8fa30c571..ccf7c5cfe 100755 --- a/src/apps/components/appinfo/runtime.ts +++ b/src/apps/components/appinfo/runtime.ts @@ -22,12 +22,13 @@ export class AppInfoRuntime extends AppProcess { } async render() { + this.getBody().setAttribute("data-prefix", "apps.AppInfo"); const targetApp = this.appStore()?.getAppSynchronous(this.targetAppId); if (!targetApp) { this.userDaemon?.sendNotification({ - title: "%apps.AppInfo.noTargetApp.title% --- %general.okay%", - message: `%apps.AppInfo.noTargetApp.message(${this.targetAppId})% --- %general.iAgree%. %general.okay%%general.okay%`, + title: "%apps.AppInfo.noTargetApp.title%", + message: `%apps.AppInfo.noTargetApp.message(${this.targetAppId})%`, image: "AppInfoIcon", timeout: 6000, }); diff --git a/src/apps/core/bootscreen/bootScreen.ts b/src/apps/core/bootscreen/bootScreen.ts index c12f6c6a1..cdad1923d 100644 --- a/src/apps/core/bootscreen/bootScreen.ts +++ b/src/apps/core/bootscreen/bootScreen.ts @@ -5,7 +5,7 @@ import { BootScreenRuntime } from "./runtime"; export const BootScreen: App = { metadata: { - name: "Boot App", + name: "%apps.bootScreen._name%", author: "Izaak Kuipers", version: "9.0.0", icon: "ComponentIcon", diff --git a/src/apps/core/loginapp/loginApp.ts b/src/apps/core/loginapp/loginApp.ts index e8b13800c..de1ea4258 100644 --- a/src/apps/core/loginapp/loginApp.ts +++ b/src/apps/core/loginapp/loginApp.ts @@ -5,7 +5,7 @@ import { LoginAppRuntime } from "./runtime"; export const LoginApp: App = { metadata: { - name: "LogonUI", + name: "%apps.loginApp._name%", author: "Izaak Kuipers", version: "9.0.0", icon: "PasswordIcon", diff --git a/src/apps/core/loginapp/runtime.ts b/src/apps/core/loginapp/runtime.ts index 8ca3bd80a..a320359e5 100755 --- a/src/apps/core/loginapp/runtime.ts +++ b/src/apps/core/loginapp/runtime.ts @@ -199,9 +199,7 @@ export class LoginAppRuntime extends AppProcess { const verbose = userDaemon.preferences().enableVerboseLogin; const broadcast = (message: string) => { if (!verbose) return; - console.warn("BROADCAST", message); this.loadingStatus.set(message); - console.warn("END BROADCAST", message); }; this.loadPersistence(); @@ -228,13 +226,15 @@ export class LoginAppRuntime extends AppProcess { broadcast("%apps.loginApp.startDaemon.notifyLoginActivity%"); await userDaemon.logActivity("login"); - broadcast("%apps.loginApp.startDaemon.startServiceHost%"); + broadcast("%apps.loginApp.startDaemon.startServiceHost.initial%"); await userDaemon.startServiceHost(async (serviceStep) => { if (serviceStep.id === "AppStorage") { - broadcast("%apps.loginApp.startDaemon.loadingApps%"); - await userDaemon.initAppStorage(userDaemon.appStorage()!, (app) => broadcast(app.metadata.name)); + broadcast("%apps.loginApp.startDaemon.loadingApps.initial%"); + await userDaemon.initAppStorage(userDaemon.appStorage()!, (app) => + broadcast(`%apps.loginApp.startDaemon.loadingApps.specific(${app.metadata.name})%`) + ); } else { - broadcast(serviceStep.name); + broadcast(`%apps.loginApp.startDaemon.startServiceHost.specific(${serviceStep.name})%`); } }); diff --git a/src/apps/user/appstore/Pages/RecentlyAdded.svelte b/src/apps/user/appstore/Pages/RecentlyAdded.svelte index 829d0ae73..b01abf978 100755 --- a/src/apps/user/appstore/Pages/RecentlyAdded.svelte +++ b/src/apps/user/appstore/Pages/RecentlyAdded.svelte @@ -6,8 +6,6 @@ const { groups, all, process }: { groups: Record; all: PartialStoreItem[]; process: AppStoreRuntime } = $props(); - - console.log(groups); {#if all?.length && groups && Object.entries(groups).length} diff --git a/src/lang/en.json b/src/lang/en.json index 5bfeb9e35..fe3b52c76 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1,6 +1,7 @@ { "apps": { "bootScreen": { + "_name": "Boot screen", "pressAnyKey": "Press any key or click to start", "safeMode": "Entering Safe Mode", "arcTerm": "Starting ArcTerm" @@ -89,6 +90,7 @@ } }, "loginApp": { + "_name": "LogonUI", "welcomeString": { "night": "Hi, go to sleep", "morning": "Good morning", @@ -109,8 +111,14 @@ "startingSync": "Starting synchronization", "profileCustomization": "Reading profile customization", "notifyLoginActivity": "Notifying login activity", - "startServiceHost": "Starting service host", - "loadingApps": "Loading apps... ", + "startServiceHost": { + "initial": "Starting service host", + "specific": "Starting {{0}}" + }, + "loadingApps": { + "initial": "Loading apps...", + "specific": "Loaded {{0}}" + }, "checkingAssoc": "Checking associations", "globalDispatch": "Connecting global dispatch", "welcome": "Welcome to ArcOS", @@ -141,12 +149,51 @@ } }, "AppInfo": { + "name": "App Info", "noTargetApp": { "title": "App not found", "message": "AppInfo couldn't find any information about \"{{0}}\". Is it installed?" }, "killAll": { "what": "ArcOS needs your permission to kill all instances of an app" + }, + "header": { + "isDisabled": "{{0}} is disabled!", + "deleteApp": "Delete app", + "launch": "Launch" + }, + "indepthInfo": { + "size": "Size", + "minSize": "Minimal Size", + "maxSize": "Maximal Size", + "controls": "Controls", + "initialPosition": "Initial Position", + "origin": "Origin", + "core": "Core", + "hidden": "Hidden", + "centered": "Centered", + "cornerOfScreen": "Corner of screen" + }, + "processInfo": { + "processes": "Processes", + "instances": "{{0}} instance(s)", + "firstPid": "First PID" + }, + "thirdPartyInfo": { + "thirdParty": "Third-party", + "signatures": "Signatures", + "hasProcess": "Has process", + "workingDirectory": "Working directory", + "entryPoint": "Entry point" + }, + "internalInfo": { + "loadTime": "Load time", + "systemVersion": "System version", + "minimalAssembly": "Minimal assembly", + "codePath": "Code path" + }, + "actions": { + "killAll": "Kill all" } } }, @@ -158,6 +205,22 @@ "next": "Next", "continue": "Continue", "decline": "Decline", - "iAgree": "I agree" + "iAgree": "I agree", + "unknown": "Unknown", + "noAuthor": "No author", + "enable": "Enable", + "disable": "Disable", + "minimize": "Minimize", + "maximize": "Maximize", + "close": "Close", + "yes": "Yes", + "no": "No", + "none": "None" + }, + "appOrigins": { + "builtin": "Built-in", + "userApps": "Third-party", + "injected": "Other", + "administrative": "Administrative" } } diff --git a/src/lang/nl.json b/src/lang/nl.json index 8b9109976..0967ef424 100644 --- a/src/lang/nl.json +++ b/src/lang/nl.json @@ -1,147 +1 @@ -{ - "apps": { - "bootScreen": { - "pressAnyKey": "Druk op een toets of klik om te starten", - "safeMode": "Veilige modus wordt geladen", - "arcTerm": "ArcTerm wordt gestart" - }, - "initialSetupWizard": { - "_name": "Wizard voor eerste installatie", - "buttons": { - "viewLicense": "Licentie openen", - "iAgree": "Ik ga akkoord", - "iClickedIt": "Ik heb geklikt", - "letsBegin": "Laten we beginnen", - "serverAllGood": "Server is in orde" - }, - "licenseConfirmation": { - "title": "Even voor de zekerheid...", - "message": "Door ArcOS te gebruiken, ga je akkoord met de licentieovereenkomst. Je mag geen van de regels in deze licentie overtreden. Doorgaan?" - }, - "viewLicense": { - "title": "ArcOS Licentie - GPLv3", - "message": "Door ArcOS te gebruiken, ga je akkoord met de GPLv3 licentie die hierin is opgenomen" - }, - "createAccount": { - "passwordMismatch": { - "title": "Je hebt een typfout gemaakt!", - "message": "De ingevoerde wachtwoorden komen niet overeen. Voer ze opnieuw in en probeer het dan opnieuw." - }, - "genericError": { - "title": "Er is iets misgegaan", - "message": "Er is een fout opgetreden bij het aanmaken van je account. Mogelijk ondervinden we technische problemen, probeer het later opnieuw." - } - }, - "checkAccountActivationError": { - "title": "Heb je op de link geklikt?", - "message": "Onze systemen vertellen me dat je account nog niet is geactiveerd. Weet je zeker dat je op de link hebt geklikt? Als je dat hebt gedaan en je ziet dit nog steeds, neem dan contact op met de ondersteuning." - }, - "page": { - "checkInbox": { - "title": "Controleer je inbox", - "subtitle": "Je hebt post!", - "message": "We hebben je een link gestuurd om je account te activeren. Open deze om verder te gaan. Controleer je spammap als je hem niet kunt vinden." - }, - "finish": { - "title": "Klaar!", - "subtitle": "Alles is ingesteld.", - "message": "Je account is succesvol ingesteld. Klik op Voltooien om ArcOS te gaan gebruiken." - }, - "freshDeployment": { - "title": "Nieuwe implementatie", - "message": "Het lijkt erop dat deze ReArc implementatie nieuw is. Controleer voordat je verder gaat of dit de bedoeling is. Als dit het geval is, klik dan op Volgende. De gebruiker die je nu gaat aanmaken wordt beschouwd als gebruiker #0 en wordt standaard ingesteld als een God Admin. De eigenaar van dit account wordt dan automatisch gezien als de eigenaar van de server. Controleer ook de serverconfiguratie:", - "note": "Opmerking: disableRegistration wordt genegeerd voor de eerste gebruiker. Deze configuratieoptie blijft behouden zodra de eerste gebruiker is aangemaakt." - }, - "identity": { - "title": "Je ArcOS Identiteit", - "subtitle": "We gebruiken deze informatie om je ArcOS account aan te maken.", - "fields": { - "displayName": "Weergavenaam", - "username": "Gebruikersnaam", - "emailAddress": "E-mailadres *", - "password": "Wachtwoord", - "passwordConfirm": "Bevestigen" - }, - "errors": { - "usernameAndEmailTaken": "Gebruikersnaam en e-mailadres zijn beide al in gebruik!", - "usernameTaken": "Gebruikersnaam is al in gebruik!", - "emailTaken": "E-mailadres is al in gebruik!", - "emailAndUsernameInvalid": "Gebruikersnaam en e-mailadres zijn beide ongeldig!", - "usernameInvalid": "Gebruikersnaam is ongeldig!", - "emailInvalid": "E-mailadres is ongeldig!", - "passwordtooWeak": "Wachtwoord is te zwak!", - "passwordweak": "Wachtwoord is zwak!", - "passwordmedium": "Wachtwoord is gemiddeld!", - "passwordstrong": "Wachtwoord is sterk!" - }, - "disclaimer": "* Je ontvangt een e-mail met een link om je account te activeren. Je weergavenaam, gebruikersnaam en wachtwoord kunnen later worden gewijzigd. Neem contact op met een beheerder om je e-mailadres te wijzigen." - }, - "license": { - "title": "Licentieovereenkomst", - "subtitle": "Eng spul, ik weet het", - "message": "Door ArcOS te gebruiken, ga je akkoord met de GPLv3 licentie." - }, - "welcome": { - "title": "Welkom", - "subtitle": "Wat fijn om kennis te maken.", - "message": "Laten we een ArcOS account voor je aanmaken. Klik op Volgende om te beginnen, of op Annuleren om terug te gaan naar het inlogscherm." - } - } - }, - "loginApp": { - "welcomeString": { - "morning": "Goedemorgen", - "afternoon": "Goedemiddag", - "evening": "Goedenavond" - }, - "errors": { - "noDaemon": "Kan daemon niet starten", - "noUserInfo": "Kan gebruikersinformatie niet opvragen", - "totpInvalid": "Je hebt geen geldige 2FA-code ingevoerd!", - "credentialIncorrect": "Gebruikersnaam of wachtwoord onjuist." - }, - "startDaemon": { - "savingToken": "Inlog opslaan", - "loadingSettings": "Persoonlijke gegevens opvragen", - "request2fa": "2FA code vragen", - "startingFilesystem": "Bestandssysteem starten", - "startingSync": "Synchronisatie starten", - "profileCustomization": "Profiel instellingen lezen", - "notifyLoginActivity": "Inlogactiviteit melden", - "startServiceHost": "Servicehost starten", - "loadingApps": "Apps laden... ", - "checkingAssoc": "Associaties controleren", - "globalDispatch": "Global dispatch verbinden", - "welcome": "Welkom bij ArcOS", - "driveNotifierWatcher": "Drive notifier watcher starten", - "shareManagement": "Delenbeheer starten", - "adminBootstrapper": "Admin bootstrapper activeren", - "statusRefresh": "Statusvernieuwing starten", - "letsGo": "Laten we gaan!", - "startingWorkspaces": "Werkruimtes starten", - "autoload": "Autorun uitvoeren" - }, - "exit": { - "logoff": "Tot ziens, ", - "shutdown": "Afsluiten...", - "restart": "Opnieuw opstarten..." - }, - "loginForm": { - "switchUser": "Gebruiker wisselen", - "noAccount": "Geen account?", - "username": "Gebruikersnaam", - "password": "Wachtwoord" - } - } - }, - "general": { - "betaPill": "BETA", - "cancel": "Annuleren", - "okay": "Oké", - "previous": "Vorige", - "next": "Volgende", - "continue": "Doorgaan", - "decline": "Afwijzen", - "iAgree": "Ik ga akkoord" - } -} +{} diff --git a/src/ts/apps/store.ts b/src/ts/apps/store.ts index a5b5de0bd..87393bb18 100755 --- a/src/ts/apps/store.ts +++ b/src/ts/apps/store.ts @@ -67,8 +67,8 @@ export const BuiltinAppImportPathAbsolutes = import.meta.glob([ export const appShortcuts: [number, AppKeyCombinations][] = []; export const AppOrigins: Record = { - builtin: "Built-in", - userApps: "Third-party", - injected: "Other", - aefs: "Administrative", + builtin: "%appOrigins.builtin%", + userApps: "%appOrigins.userApps%", + injected: "%appOrigins.injected%", + aefs: "%appOrigins.administrative%", }; diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index 2a029e1ba..c1d526ecf 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -8,7 +8,7 @@ import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; export class I18n extends KernelModule { REGEX = /%(?[\w.=\-]+)(?:\((?(.*?))\)|)%/gm; - language: string = "en"; + language: string = "nl"; dispatch: SystemDispatchType; observer: MutationObserver | null = null; TARGET?: HTMLDivElement; @@ -30,47 +30,70 @@ export class I18n extends KernelModule { this.startObserver(document.querySelector("#appRenderer")!); } - replaceInNode(node: Node): void { - if (node.nodeType === node.TEXT_NODE) { - const str = node.textContent ?? ""; - const results = [...str.matchAll(this.REGEX)].map(({ groups, [0]: key }) => ({ - id: groups?.id, - inlays: groups?.inlays ? groups.inlays.split(",") : [], - key, - })); - - if (!results.length) { - return; - } + translateString(str: string, prefix?: string): string | undefined { + const results = [...str.matchAll(this.REGEX)].map(({ groups, [0]: key }) => ({ + id: groups?.id, + inlays: groups?.inlays ? groups.inlays.split(",") : [], + key, + })); - let resultString = str; - for (const result of results) { - const value = getJsonHierarchy(this.LANGUAGES[this.language], result.id ?? ""); + if (!results.length) return undefined; - if (!result.id || !value) { - resultString = resultString.replace(result.key, "??"); + let resultString = str; - continue; - } + for (const result of results) { + const fullId = prefix ? `${prefix}.${result.id}` : result.id; - let partialResultString = value as string; + const value = + getJsonHierarchy(this.LANGUAGES[this.language], fullId ?? "") || + getJsonHierarchy(this.LANGUAGES[this.language], result.id ?? ""); - if (result.inlays) { - for (let i = 0; i < result.inlays.length; i++) { - partialResultString = partialResultString.replace(`{{${i}}}`, result.inlays[i]); - } - } + if (!result.id || !value) { + resultString = resultString.replace(result.key, "??"); + continue; + } + + let partialResultString = value as string; - resultString = resultString.replace(result.key, partialResultString); + if (result.inlays) { + for (let i = 0; i < result.inlays.length; i++) { + partialResultString = partialResultString.replace(`{{${i}}}`, result.inlays[i]); + } } + resultString = resultString.replace(result.key, partialResultString); + } + + return resultString; + } + + replaceInNode(node: Node): void { + if (node.nodeType === node.TEXT_NODE) { + const str = node.textContent ?? ""; + const prefix = node.parentElement?.closest("[data-prefix]")?.getAttribute("data-prefix") ?? undefined; + const resultString = this.translateString(str, prefix); + + if (!resultString) return; + if (node.parentElement) { - node.parentElement.setAttribute(`data-i18n-original`, str); - node.parentElement.textContent = resultString; + node.parentElement.setAttribute("data-i18n-original", str); + node.parentElement.innerHTML = resultString; } - - console.warn(str, resultString); } else if (node.nodeType === node.ELEMENT_NODE) { + const el = node as HTMLElement; + const prefix = el.closest("[data-prefix]")?.getAttribute("data-prefix") ?? undefined; + + for (const attr of this.TRANSLATABLE_ATTRIBUTES) { + const raw = el.getAttribute(attr); + if (!raw) continue; + + const resultString = this.translateString(raw, prefix); + if (!resultString) continue; + + el.setAttribute(`data-i18n-original-${attr}`, raw); + el.setAttribute(attr, resultString); + } + Array.from(node.childNodes).forEach((n) => this.replaceInNode(n)); } } diff --git a/src/ts/writable.ts b/src/ts/writable.ts index e2b96d664..5bab3a384 100755 --- a/src/ts/writable.ts +++ b/src/ts/writable.ts @@ -1,4 +1,6 @@ import { get, writable } from "svelte/store"; +import { getKMod } from "./env"; +import { I18n } from "./kernel/mods/i18n"; /** Callback to inform of a value updates. */ export type Subscriber = (value: T) => void; @@ -39,8 +41,22 @@ export type StringStore = ReadableStore; export type NumberStore = ReadableStore; export function Store(initial?: T): ReadableStore { + function translateValue(v: any) { + if (typeof v !== "string") return v; + + const i18n = getKMod("i18n", true); + + return i18n?.translateString(v) || v; + } + const store = writable(initial); - const obj = { ...store, get: () => get(store) }; + const obj = { + ...store, + get: () => get(store), + set: (v: any) => store.set(translateValue(v)), + update: (v: (v: any) => any) => store.set(translateValue(v(get(store)))), + }; + const fn = () => obj.get(); fn.get = obj.get; From d064e204dfe406477ccb878f3f7b7bd31c71f6da Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 21:12:26 +0200 Subject: [PATCH 06/17] Translate AppInstaller --- .../appinstaller/AppInstaller/Actions.svelte | 10 ++--- .../appinstaller/AppInstaller/Header.svelte | 12 +++--- .../appinstaller/AppInstaller/Log.svelte | 12 +++--- src/apps/components/appinstaller/runtime.ts | 19 ++++----- src/lang/en.json | 40 +++++++++++++++++++ src/ts/kernel/mods/i18n/index.ts | 5 +-- 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/src/apps/components/appinstaller/AppInstaller/Actions.svelte b/src/apps/components/appinstaller/AppInstaller/Actions.svelte index e836b2ba6..6c943fcb4 100755 --- a/src/apps/components/appinstaller/AppInstaller/Actions.svelte +++ b/src/apps/components/appinstaller/AppInstaller/Actions.svelte @@ -7,12 +7,12 @@
{#if $failReason} - + {:else if $installing || $completed} - - + + {:else} - - + + {/if}
diff --git a/src/apps/components/appinstaller/AppInstaller/Header.svelte b/src/apps/components/appinstaller/AppInstaller/Header.svelte index 32b948dc4..020c1d4b0 100755 --- a/src/apps/components/appinstaller/AppInstaller/Header.svelte +++ b/src/apps/components/appinstaller/AppInstaller/Header.svelte @@ -11,24 +11,24 @@

{#if $completed} - Package installed! + %header.title.installed% {:else if $installing} - Installing package... + %header.title.installing% {:else if $failReason} - Installation failed + %header.title.failed% {:else} {metadata?.name} {/if}

{#if $completed} - Click Open now to launch the app + %header.subtitle.completed% {:else if $installing} - {metadata?.name} by {metadata?.author} + %header.subtitle.installing({metadata?.name}::{metadata?.author})% {:else if $failReason} {$failReason} {:else} - {metadata?.author} - {metadata?.version} + %header.subtitle.generic({metadata?.author}::{metadata?.version})% {/if}

diff --git a/src/apps/components/appinstaller/AppInstaller/Log.svelte b/src/apps/components/appinstaller/AppInstaller/Log.svelte index e517446ac..a59f0357e 100755 --- a/src/apps/components/appinstaller/AppInstaller/Log.svelte +++ b/src/apps/components/appinstaller/AppInstaller/Log.svelte @@ -18,21 +18,21 @@ {#if !Object.entries($status).length && !$installing}
-

Ready to install

-

Click Install to install this package.

+

%readyToInstall.title%

+

%readyToInstall.message%

{:else} {#each Object.entries($status) as [uuid, item] (uuid)}

{#if item.type === "file"} - Writing file + %logType.file% {:else if item.type === "mkdir"} - Creating directory + %logType.mkdir% {:else if item.type === "registration"} - Registering + %logType.registration% {:else} - Status + %logType.generic% {/if}

{item.content}

diff --git a/src/apps/components/appinstaller/runtime.ts b/src/apps/components/appinstaller/runtime.ts index ee98aa1d1..b97746c55 100755 --- a/src/apps/components/appinstaller/runtime.ts +++ b/src/apps/components/appinstaller/runtime.ts @@ -34,9 +34,9 @@ export class AppInstallerRuntime extends AppProcess { // Should never happen unless nik fucked something up (yes, nik) MessageBox( { - title: "Can't install package", - message: "The Distribution Service isn't running anymore. Please restart ArcOS to fix this problem.", - buttons: [{ caption: "Okay", action: () => {}, suggested: true }], + title: "%apps.AppInstaller.noDistrib.title%", + message: "%apps.AppInstaller.noDistrib.message%", + buttons: [{ caption: "%general.okay%", action: () => {}, suggested: true }], image: "ErrorIcon", sound: "arcos.dialog.error", }, @@ -50,24 +50,25 @@ export class AppInstallerRuntime extends AppProcess { } async render() { + this.getBody().setAttribute("data-prefix", "apps.AppInstaller"); + if (!this.userPreferences().security.enableThirdParty) { // The user has to allow TPAs explicitly MessageBox( { - title: "Can't install app", - message: - "Third-party apps aren't enabled on your account. Please enable third-party apps in the Settings to install this app.", + title: "%apps.AppInstaller.noEnableThirdParty.title%", + message: "%apps.AppInstaller.noEnableThirdParty.message%", image: "AppsIcon", sound: "arcos.dialog.warning", buttons: [ { - caption: "Take me there", + caption: "%apps.AppInstaller.noEnableThirdParty.takeMeThere%", action: () => { this.userDaemon?.spawnApp("systemSettings", +this.env.get("shell_pid"), "apps"); }, }, { - caption: "Okay", + caption: "%general.okay%", action: () => {}, suggested: true, }, @@ -88,7 +89,7 @@ export class AppInstallerRuntime extends AppProcess { async revert() { // I don't know how well this revert works because a package install // has never really errored for me before. - const gli = await this.userDaemon?.GlobalLoadIndicator("Rolling back changes...", this.pid); + const gli = await this.userDaemon?.GlobalLoadIndicator("%apps.AppInstaller.rollback%", this.pid); try { await this.fs.deleteItem(this.metadata!.installLocation); diff --git a/src/lang/en.json b/src/lang/en.json index fe3b52c76..8c9b4df0e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -195,6 +195,46 @@ "actions": { "killAll": "Kill all" } + }, + "AppInstaller": { + "noDistrib": { + "title": "Can't install package", + "message": "The Distribution Service isn't running anymore. Please restart ArcOS to fix this problem." + }, + "noEnableThirdParty": { + "title": "Can't install app", + "message": "Third-party apps aren't enabled on your account. Please enable third-party apps in the Settings app to install this app.", + "takeMeThere": "Take me there" + }, + "rollback": "Rolling back changes...", + "header": { + "title": { + "installed": "Package installed!", + "installing": "Installing package...", + "failed": "Installation failed" + }, + "subtitle": { + "completed": "Click Open now to launch the app", + "installing": "{{0}} by {{1}}", + "generic": "{{0}} - {{1}}" + } + }, + "actions": { + "revert": "Revert", + "openNow": "Open now", + "done": "Done", + "install": "Install" + }, + "readyToInstall": { + "title": "Ready to install", + "message": "Click Install to install this package." + }, + "logType": { + "file": "Writing file", + "mkdir": "Creating directory", + "registration": "registering", + "generic": "Status" + } } }, "general": { diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index c1d526ecf..0874a3ee7 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -1,9 +1,8 @@ import EnglishLanguage from "$lang/en.json"; import DutchLanguage from "$lang/nl.json"; -import { getKMod, KernelStack } from "$ts/env"; +import { getKMod } from "$ts/env"; import { getJsonHierarchy } from "$ts/hierarchy"; import { KernelModule } from "$ts/kernel/module"; -import { detectJavaScript } from "$ts/util"; import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; export class I18n extends KernelModule { @@ -33,7 +32,7 @@ export class I18n extends KernelModule { translateString(str: string, prefix?: string): string | undefined { const results = [...str.matchAll(this.REGEX)].map(({ groups, [0]: key }) => ({ id: groups?.id, - inlays: groups?.inlays ? groups.inlays.split(",") : [], + inlays: groups?.inlays ? groups.inlays.split("::") : [], key, })); From 2a19f39949f0304914a507b905652a7c8918c827 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 21:33:50 +0200 Subject: [PATCH 07/17] Translate AppPreInstall --- .../apppreinstall/AppPreinstall.svelte | 10 +++--- src/apps/components/apppreinstall/runtime.ts | 35 +++++++++---------- src/lang/en.json | 33 ++++++++++++++++- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/apps/components/apppreinstall/AppPreinstall.svelte b/src/apps/components/apppreinstall/AppPreinstall.svelte index 4248a7cf0..6240c8857 100755 --- a/src/apps/components/apppreinstall/AppPreinstall.svelte +++ b/src/apps/components/apppreinstall/AppPreinstall.svelte @@ -10,17 +10,17 @@

{$metadata.name}

-

Do you want to install this package?

+

%header.title%

{$metadata.description}

-

Author

+

%info.author%

{$metadata.author}

-

Version

+

%info.version%

{$metadata.version}

@@ -28,9 +28,9 @@
- +
{:else}
diff --git a/src/apps/components/apppreinstall/runtime.ts b/src/apps/components/apppreinstall/runtime.ts index b0bc00467..054e1eaee 100755 --- a/src/apps/components/apppreinstall/runtime.ts +++ b/src/apps/components/apppreinstall/runtime.ts @@ -28,23 +28,24 @@ export class AppPreInstallRuntime extends AppProcess { } async render() { + this.getBody().setAttribute("data-prefix", "apps.AppPreInstall"); + if (!this.userPreferences().security.enableThirdParty) { MessageBox( { - title: "Can't install app", - message: - "Third-party apps aren't enabled on your account. Please enable third-party apps in the Settings to install this app.", + title: "%apps.AppPreInstall.noEnableThirdParty.title%", + message: "%apps.AppPreInstall.noEnableThirdParty.message%", image: "AppsIcon", sound: "arcos.dialog.warning", buttons: [ { - caption: "Take me there", + caption: "%apps.AppPreInstall.noEnableThirdParty.takeMeThere%", action: () => { this.userDaemon?.spawnApp("systemSettings", +this.env.get("shell_pid"), "apps"); }, }, { - caption: "Okay", + caption: "%general.okay%", action: () => {}, suggested: true, }, @@ -62,7 +63,7 @@ export class AppPreInstallRuntime extends AppProcess { { type: "size", icon: "DownloadIcon", - caption: "Reading ArcOS package", + caption: "%apps.AppPreInstall.readingPackage%", subtitle: this.pkgPath, }, +this.env.get("shell_pid") @@ -78,32 +79,30 @@ export class AppPreInstallRuntime extends AppProcess { await prog?.stop(); if (!content) { - return this.fail("The package contents could not be read"); + return this.fail("%apps.AppPreInstall.errors.noContents%"); } this.zip = new JSZip(); const buffer = await this.zip.loadAsync(content, {}); if (!buffer.files["_metadata.json"] || !buffer.files["payload/_app.tpa"]) { - return this.fail("Package is corrupt; missing package or app metadata."); + return this.fail("%apps.AppPreInstall.errors.missingFiles%"); } const metaBinary = await buffer.files["_metadata.json"].async("arraybuffer"); const metadata = tryJsonParse(arrayToText(metaBinary)); if (!metadata || typeof metadata === "string") { - return this.fail("The package metadata could not be read"); + return this.fail("%apps.AppPreInstall.errors.noMeta%"); } if (metadata.appId.includes(".") || metadata.appId.includes("-")) { - return this.fail( - "The application ID is malformed: it contains periods or dashes. If you're the creator of the app, be sure to use the suggested format for application IDs." - ); + return this.fail("%apps.AppPreInstall.errors.appIdMalformed%"); } this.metadata.set(metadata); } catch { - return this.fail("Filesystem error"); + return this.fail("%apps.AppPreInstall.errors.fsError%"); } } @@ -113,9 +112,9 @@ export class AppPreInstallRuntime extends AppProcess { fail(reason: string) { MessageBox( { - title: "Failed to open package", - message: `ArcOS failed to open the specified package. ${reason}`, - buttons: [{ caption: "Okay", action: () => {}, suggested: true }], + title: "%apps.AppPreInstall.fail.title%", + message: `%apps.AppPreInstall.fail.messagePartial% ${reason}`, + buttons: [{ caption: "%general.okay%", action: () => {}, suggested: true }], image: "ErrorIcon", sound: "arcos.dialog.error", }, @@ -128,9 +127,9 @@ export class AppPreInstallRuntime extends AppProcess { async install() { const meta = this.metadata(); const elevated = await this.userDaemon?.manuallyElevate({ - what: "ArcOS wants to install an application", + what: "%apps.AppPreInstall.elevation.what%", title: meta.name, - description: `${meta.author} - ${meta.version}`, + description: `%apps.AppPreInstall.elevation.description(${meta.author}::${meta.version})%`, image: "ArcAppMimeIcon", level: ElevationLevel.medium, }); diff --git a/src/lang/en.json b/src/lang/en.json index 8c9b4df0e..2e2616831 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -232,9 +232,40 @@ "logType": { "file": "Writing file", "mkdir": "Creating directory", - "registration": "registering", + "registration": "Registering", "generic": "Status" } + }, + "AppPreInstall": { + "noEnableThirdParty": { + "title": "Can't install app", + "message": "Third-party apps aren't enabled on your account. Please enable third-party apps in the Settings app to install this app.", + "takeMeThere": "Take me there" + }, + "readingPackage": "Reading ArcOS package", + "errors": { + "noContents": "The package contents could not be read", + "noMeta": "The package metadata could not be read", + "missingFiles": "Package is corrupt; missing package or app metadata.", + "appIdMalformed": "The application ID is malformed: it contains periods or dashes. If you're the creator of the app, be sure to use the suggested format for application IDs.", + "fsError": "Filesystem error" + }, + "fail": { + "title": "Failed to open package", + "messagePartial": "ArcOS failed to open the specified package." + }, + "elevation": { + "what": "ArcOS wants to install an application", + "description": "{{0}} - {{1}}" + }, + "header": { + "title": "Do you want to install this package?" + }, + "info": { + "author": "Author", + "version": "Version" + }, + "installPackage": "Install package" } }, "general": { From eefa3e6e5c5f798e30a9ee74479e8d6d2d2353f0 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 22:01:14 +0200 Subject: [PATCH 08/17] Translate ArcFindProc --- src/apps/components/arcfind/runtime.ts | 33 ++++++++++++++++---------- src/lang/en.json | 22 +++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/apps/components/arcfind/runtime.ts b/src/apps/components/arcfind/runtime.ts index 17424e5a1..993d8215c 100755 --- a/src/apps/components/arcfind/runtime.ts +++ b/src/apps/components/arcfind/runtime.ts @@ -1,5 +1,7 @@ import { AppProcess } from "$ts/apps/process"; import { isPopulatable } from "$ts/apps/util"; +import { getKMod } from "$ts/env"; +import { I18n } from "$ts/kernel/mods/i18n"; import { UserPaths } from "$ts/server/user/store"; import { UUID } from "$ts/uuid"; import { Store } from "$ts/writable"; @@ -30,10 +32,10 @@ export class ArcFindRuntime extends AppProcess { let excludeShortcuts = preferences.searchOptions.excludeShortcuts; this.userPreferences.subscribe((v) => { if (v.searchOptions.excludeShortcuts !== excludeShortcuts) { - this.refresh() - excludeShortcuts = v.searchOptions.excludeShortcuts + this.refresh(); + excludeShortcuts = v.searchOptions.excludeShortcuts; } - }) + }); } //#endregion @@ -59,24 +61,24 @@ export class ArcFindRuntime extends AppProcess { if (sources.power) items.push( { - caption: "Shut down", - description: "Leave the desktop and turn off ArcOS", + caption: "%apps.ArcFindProc.powerOptions.shutdown.caption%", + description: "%apps.ArcFindProc.powerOptions.shutdown.description%", image: this.getIconCached("ShutdownIcon"), action: () => { this.userDaemon?.shutdown(); }, }, { - caption: "Restart", - description: "Leave the desktop and restart ArcOS", + caption: "%apps.ArcFindProc.powerOptions.restart.caption%", + description: "%apps.ArcFindProc.powerOptions.restart.description%", image: this.getIconCached("RestartIcon"), action: () => { this.userDaemon?.restart(); }, }, { - caption: "Log off", - description: "Leave the desktop and log out ArcOS", + caption: "%apps.ArcFindProc.powerOptions.logoff.caption%", + description: "%apps.ArcFindProc.powerOptions.logoff.description%", image: this.getIconCached("LogoutIcon"), action: () => { this.userDaemon?.logoff(); @@ -103,7 +105,7 @@ export class ArcFindRuntime extends AppProcess { if (preferences.searchOptions.excludeShortcuts && !!file.shortcut) continue; result.push({ caption: file.shortcut ? file.shortcut.name : file.name, - description: file.shortcut ? `Shortcut - ${file.path}` : file.path, + description: file.shortcut ? `%apps.ArcFindProc.fsSupplier.shortcut(${file.path})%` : file.path, action: () => { this.userDaemon?.openFile(file.path, file.shortcut); }, @@ -130,7 +132,7 @@ export class ArcFindRuntime extends AppProcess { ) { result.push({ caption: app.metadata.name, - description: `By ${app.metadata.author}`, + description: `%apps.ArcFindProc.appSupplier(${app.metadata.author})%`, image: this.getIconCached(`@app::${app.id}`), action: () => { this.spawnApp(app.id, this.pid); @@ -179,7 +181,14 @@ export class ArcFindRuntime extends AppProcess { keys: ["caption", "description"], }; - const fuse = new Fuse(this.searchItems, options); + const fuse = new Fuse( + this.searchItems.map((v) => ({ + ...v, + caption: getKMod("i18n").translateString(v.caption) || v.caption, + description: getKMod("i18n").translateString(v.description || "") || v.description, + })), + options + ); const result = fuse.search(query); return result.map((r) => ({ ...r, id: UUID() })); // Add a UUID to each search result diff --git a/src/lang/en.json b/src/lang/en.json index 2e2616831..ef19fdf4c 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -266,6 +266,28 @@ "version": "Version" }, "installPackage": "Install package" + }, + "ArcFindProc": { + "fsSupplier": { + "shortcut": "Shortcut - {{0}}" + }, + "appSupplier": { + "description": "By {{0}}" + }, + "powerOptions": { + "shutdown": { + "caption": "Shut down", + "description": "Leave the desktop and turn off ArcOS" + }, + "restart": { + "caption": "Restart", + "description": "Leave the desktop and restart ArcOS" + }, + "logoff": { + "caption": "Log off", + "description": "Leave the desktop and log out of ArcOS" + } + } } }, "general": { From e12d22e686d19b6291b7e9414cbb1a2bd49f3df1 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 22:08:25 +0200 Subject: [PATCH 09/17] Translate contextMenu --- src/apps/components/contextmenu/system.ts | 61 ++++++++++++----------- src/lang/en.json | 21 ++++++++ src/ts/kernel/mods/i18n/index.ts | 2 +- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/apps/components/contextmenu/system.ts b/src/apps/components/contextmenu/system.ts index 4d9f758ce..48154b258 100755 --- a/src/apps/components/contextmenu/system.ts +++ b/src/apps/components/contextmenu/system.ts @@ -1,12 +1,10 @@ import { AppProcess } from "$ts/apps/process"; import { KernelStack } from "$ts/env"; -import { Log } from "$ts/logging"; import { UserDaemon } from "$ts/server/user/daemon"; import type { AppContextMenu } from "$types/app"; import type { ContextMenuRuntime } from "./runtime"; export function WindowSystemContextMenu(runtime: ContextMenuRuntime): AppContextMenu { - let userDaemon = runtime.userDaemon as UserDaemon; let workspaces = userDaemon?.preferences().workspaces.desktops; let currentWorkspace = userDaemon?.preferences().workspaces.index; @@ -14,23 +12,23 @@ export function WindowSystemContextMenu(runtime: ContextMenuRuntime): AppContext return { "_window-titlebar": [ { - caption: "App Info", + caption: "%apps.contextMenu.system.windowTitlebar.appInfo%", icon: "info", action: (proc: AppProcess) => { proc.spawnOverlayApp("AppInfo", +proc.env.get("shell_pid"), proc?.app.id); }, }, { - caption: "Process info", + caption: "%apps.contextMenu.system.windowTitlebar.processInfo%", icon: "cog", action: (proc: AppProcess) => { proc.spawnOverlayApp("ProcessInfoApp", +proc.env.get("shell_pid"), proc); }, }, { sep: true }, - + { - caption: "Minimize", + caption: "%general.minimize%", action: (proc: AppProcess) => { KernelStack().renderer?.toggleMinimize(proc?.pid); }, @@ -39,7 +37,7 @@ export function WindowSystemContextMenu(runtime: ContextMenuRuntime): AppContext isActive: (proc: AppProcess) => !!proc?.getWindow()?.classList.contains("minimized"), }, { - caption: "Maximize", + caption: "%general.maximize%", action: (proc: AppProcess) => { KernelStack().renderer?.unsnapWindow(proc?.pid); KernelStack().renderer?.toggleMaximize(proc?.pid); @@ -50,100 +48,107 @@ export function WindowSystemContextMenu(runtime: ContextMenuRuntime): AppContext }, { sep: true }, { - caption: "Window snapping", + caption: "%apps.contextMenu.system.windowTitlebar.windowSnapping%", icon: "fullscreen", disabled: (proc: AppProcess) => !proc?.app.data.controls.maximize, isActive: (proc: AppProcess) => !!proc?.getWindow()?.classList.contains("snapped"), subItems: [ { - caption: "None", + caption: "%general.none%", icon: "x", action: (proc: AppProcess) => KernelStack().renderer?.unsnapWindow(proc?.pid), }, { sep: true }, { - caption: "Left", + caption: "%apps.contextMenu.system.windowTitlebar.snappingLeft%", icon: "arrow-left", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "left"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "left", }, { - caption: "Right", + caption: "%apps.contextMenu.system.windowTitlebar.snappingRight%", icon: "arrow-right", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "right"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "right", }, { sep: true }, { - caption: "Top", + caption: "%apps.contextMenu.system.windowTitlebar.snappingTop%", icon: "arrow-up", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "top"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "top", }, { - caption: "Bottom", + caption: "%apps.contextMenu.system.windowTitlebar.snappingBottom%", icon: "arrow-down", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "bottom"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "bottom", }, { sep: true }, { - caption: "Top Left", + caption: "%apps.contextMenu.system.windowTitlebar.snappingTopLeft%", icon: "arrow-up-left", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "top-left"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "top-left", }, { - caption: "Top Right", + caption: "%apps.contextMenu.system.windowTitlebar.snappingTopRight%", icon: "arrow-up-right", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "top-right"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "top-right", }, { sep: true }, { - caption: "Bottom Left", + caption: "%apps.contextMenu.system.windowTitlebar.snappingBottomLeft%", icon: "arrow-down-left", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "bottom-left"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "bottom-left", }, { - caption: "Bottom Right", + caption: "%apps.contextMenu.system.windowTitlebar.snappingBottomRight%", icon: "arrow-down-right", action: (proc: AppProcess) => KernelStack().renderer?.snapWindow(proc?.pid, "bottom-right"), isActive: (proc: AppProcess) => proc?.getWindow()?.dataset.snapstate === "bottom-right", }, ], - },{ - caption: "Move to workspace", + }, + { + caption: "%apps.contextMenu.system.windowTitlebar.moveWorkspace%", icon: "rotate-ccw-square", subItems: [ - { - caption: "Left workspace", + { + caption: "%apps.contextMenu.system.windowTitlebar.moveWorkspaceLeft%", icon: "arrow-left", action: (proc: AppProcess) => { if (!proc?.pid) return; - userDaemon?.moveWindow(proc.pid, workspaces[(currentWorkspace - 1 >= 0) ? currentWorkspace - 1 : workspaces.length - 1]?.uuid); + userDaemon?.moveWindow( + proc.pid, + workspaces[currentWorkspace - 1 >= 0 ? currentWorkspace - 1 : workspaces.length - 1]?.uuid + ); }, disabled: (proc: AppProcess) => { - return !workspaces[(currentWorkspace - 1)] || !proc?.pid; + return !workspaces[currentWorkspace - 1] || !proc?.pid; }, }, - { - caption: "Right workspace", + { + caption: "%apps.contextMenu.system.windowTitlebar.moveWorkspaceRight%", icon: "arrow-right", action: (proc: AppProcess) => { if (!proc?.pid) return; - userDaemon?.moveWindow(proc.pid, workspaces[(currentWorkspace + 1 <= workspaces.length - 1) ? currentWorkspace + 1 : 0]?.uuid); + userDaemon?.moveWindow( + proc.pid, + workspaces[currentWorkspace + 1 <= workspaces.length - 1 ? currentWorkspace + 1 : 0]?.uuid + ); }, disabled: (proc: AppProcess) => { - return !workspaces[(currentWorkspace + 1)] || !proc?.pid; + return !workspaces[currentWorkspace + 1] || !proc?.pid; }, }, ], }, { sep: true }, { - caption: "Close", + caption: "%general.close%", action: (proc: AppProcess) => { proc.closeWindow(); }, diff --git a/src/lang/en.json b/src/lang/en.json index ef19fdf4c..ccc694526 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -288,6 +288,27 @@ "description": "Leave the desktop and log out of ArcOS" } } + }, + "contextMenu": { + "_name": "Context Menu", + "system": { + "windowTitlebar": { + "appInfo": "App Info", + "processInfo": "Process Info", + "windowSnapping": "Window snapping", + "snappingLeft": "Left", + "snappingRight": "Right", + "snappingTop": "Top", + "snappingBottom": "Bottom", + "snappingTopLeft": "Top Left", + "snappingTopRight": "Top Right", + "snappingBottomLeft": "Bottom Left", + "snappingBottomRight": "Bottom Right", + "moveWorkspace": "Move to workspace", + "moveWorkspaceLeft": "Left workspace", + "moveWorkspaceRight": "Right workspace" + } + } } }, "general": { diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index 0874a3ee7..da4e2609f 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -7,7 +7,7 @@ import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; export class I18n extends KernelModule { REGEX = /%(?[\w.=\-]+)(?:\((?(.*?))\)|)%/gm; - language: string = "nl"; + language: string = "en"; dispatch: SystemDispatchType; observer: MutationObserver | null = null; TARGET?: HTMLDivElement; From 299fdfb6720c79a1c37d7b5f200bd7ba805d7175 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 22:19:37 +0200 Subject: [PATCH 10/17] Translate DriveInfo --- .../components/driveinfo/DriveInfo.svelte | 5 ++-- src/apps/components/driveinfo/DriveInfo.ts | 2 +- .../driveinfo/DriveInfo/AdvancedInfo.svelte | 12 ++++---- .../driveinfo/DriveInfo/Quota.svelte | 4 +-- .../driveinfo/DriveInfo/Usage.svelte | 2 +- src/apps/components/driveinfo/runtime.ts | 5 ++++ src/lang/en.json | 30 ++++++++++++++++++- 7 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/apps/components/driveinfo/DriveInfo.svelte b/src/apps/components/driveinfo/DriveInfo.svelte index 16eaa8577..f75059ab0 100755 --- a/src/apps/components/driveinfo/DriveInfo.svelte +++ b/src/apps/components/driveinfo/DriveInfo.svelte @@ -30,7 +30,8 @@ {/if}
{#if isUserFs} - + {/if} - +
diff --git a/src/apps/components/driveinfo/DriveInfo.ts b/src/apps/components/driveinfo/DriveInfo.ts index 980336371..78b6e85cf 100644 --- a/src/apps/components/driveinfo/DriveInfo.ts +++ b/src/apps/components/driveinfo/DriveInfo.ts @@ -5,7 +5,7 @@ import { DriveInfoRuntime } from "./runtime"; const DriveInfoApp: App = { metadata: { - name: "Drive Info", + name: "%apps.DriveInfo._name%", version: "1.0.0", author: "Izaak Kuipers", icon: "DriveIcon", diff --git a/src/apps/components/driveinfo/DriveInfo/AdvancedInfo.svelte b/src/apps/components/driveinfo/DriveInfo/AdvancedInfo.svelte index 09e722545..4b9370dce 100755 --- a/src/apps/components/driveinfo/DriveInfo/AdvancedInfo.svelte +++ b/src/apps/components/driveinfo/DriveInfo/AdvancedInfo.svelte @@ -6,25 +6,25 @@
-

ID

+

%advanced.id%

{drive.uuid}

-

Mountpoint

+

%advanced.mountpoint%

{drive.driveLetter || drive.uuid}:/

-

Label

+

%advanced.label%

{drive.label}

-

Identifies As

+

%advanced.identifiesAs%

{drive.IDENTIFIES_AS}

-

Flags

+

%advanced.flags%

- {`${drive.BUSY ? "Busy" : ""} ${drive.FIXED ? "Fixed" : ""} ${drive.READONLY ? "Readonly" : ""} ${drive.REMOVABLE ? "Removable" : ""}`.trim()} + {`${drive.BUSY ? "%advanced.busy%" : ""} ${drive.FIXED ? "%advanced.fixed%" : ""} ${drive.READONLY ? "%advanced.readonly%" : ""} ${drive.REMOVABLE ? "%advanced.removable%" : ""}`.trim()}

diff --git a/src/apps/components/driveinfo/DriveInfo/Quota.svelte b/src/apps/components/driveinfo/DriveInfo/Quota.svelte index 022b99bd3..c03cc83fb 100755 --- a/src/apps/components/driveinfo/DriveInfo/Quota.svelte +++ b/src/apps/components/driveinfo/DriveInfo/Quota.svelte @@ -10,7 +10,7 @@

- Used ({quota.percentage.toFixed(0)}%) + %quota.used({quota.percentage.toFixed(0)})%

{formatBytes(quota.used)}

@@ -19,7 +19,7 @@

- Free ({(100 - quota.percentage).toFixed(0)}%) + %quota.free({(100 - quota.percentage).toFixed(0)})%

{formatBytes(quota.free)}

diff --git a/src/apps/components/driveinfo/DriveInfo/Usage.svelte b/src/apps/components/driveinfo/DriveInfo/Usage.svelte index 18742cf83..7f655d21f 100755 --- a/src/apps/components/driveinfo/DriveInfo/Usage.svelte +++ b/src/apps/components/driveinfo/DriveInfo/Usage.svelte @@ -18,7 +18,7 @@

- {id.replace(id[0], id[0].toUpperCase())} ({percentage.toFixed(1)}%) + %legend.{id}({percentage.toFixed(1)})%

{formatBytes((usage.sizes as any)[id] || 0)}

diff --git a/src/apps/components/driveinfo/runtime.ts b/src/apps/components/driveinfo/runtime.ts index 72cf2c089..d2ee06fa1 100755 --- a/src/apps/components/driveinfo/runtime.ts +++ b/src/apps/components/driveinfo/runtime.ts @@ -4,6 +4,7 @@ import { ServerDrive } from "$ts/drives/server"; import { USERFS_UUID } from "$ts/env"; import type { AppProcessData } from "$types/app"; import type { UserQuota } from "$types/fs"; +import type { RenderArgs } from "$types/process"; import type { CategorizedDiskUsage } from "$types/user"; export class DriveInfoRuntime extends AppProcess { @@ -31,5 +32,9 @@ export class DriveInfoRuntime extends AppProcess { if (this.isUserFs) this.usage = await this.userDaemon?.determineCategorizedDiskUsage(); } + render(args: RenderArgs) { + this.getBody().setAttribute("data-prefix", "apps.DriveInfo"); + } + //#endregion } diff --git a/src/lang/en.json b/src/lang/en.json index ccc694526..3336887a1 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -149,7 +149,7 @@ } }, "AppInfo": { - "name": "App Info", + "_name": "App Info", "noTargetApp": { "title": "App not found", "message": "AppInfo couldn't find any information about \"{{0}}\". Is it installed?" @@ -309,6 +309,34 @@ "moveWorkspaceRight": "Right workspace" } } + }, + "DriveInfo": { + "_name": "Drive Info", + "quota": { + "used": "Used ({{0}}%)", + "free": "Free ({{0}}%)" + }, + "legend": { + "system": "System ({{0}}%)", + "trash": "Trash ({{0}}%)", + "home": "Home ({{0}}%)", + "apps": "Apps ({{0}}%)" + }, + "advanced": { + "id": "ID", + "mountpoint": "Mountpoint", + "label": "Label", + "identifiesAs": "Identifies As", + "flags": "Flags", + "busy": "Busy", + "fixed": "Fixed", + "readonly": "Readonly", + "removable": "Removable" + }, + "actions": { + "simple": "Simple", + "advanced": "Advanced" + } } }, "general": { From 2368b73d4f09b10e72a788d21debc3fe404f9287 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 22:23:39 +0200 Subject: [PATCH 11/17] Translate ExitApp --- src/apps/components/exit/Exit.svelte | 8 ++++---- src/apps/components/exit/ExitApp.ts | 2 +- src/apps/components/exit/runtime.ts | 5 +++++ src/apps/components/exit/store.ts | 6 +++--- src/lang/en.json | 15 ++++++++++++++- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/apps/components/exit/Exit.svelte b/src/apps/components/exit/Exit.svelte index c70c01fe2..64e5184f3 100755 --- a/src/apps/components/exit/Exit.svelte +++ b/src/apps/components/exit/Exit.svelte @@ -7,8 +7,8 @@
-

Exit ArcOS

-

What's your escape route?

+

%header.title%

+

%header.message%

@@ -26,7 +26,7 @@
- +
diff --git a/src/apps/components/exit/ExitApp.ts b/src/apps/components/exit/ExitApp.ts index 4d9edc7ef..342d3af8c 100644 --- a/src/apps/components/exit/ExitApp.ts +++ b/src/apps/components/exit/ExitApp.ts @@ -5,7 +5,7 @@ import { ExitRuntime } from "./runtime"; const ExitApp: App = { metadata: { - name: "Exit", + name: "%apps.ExitApp._name%", author: "Izaak Kuipers", version: "6.0.0", icon: "ShutdownIcon", diff --git a/src/apps/components/exit/runtime.ts b/src/apps/components/exit/runtime.ts index efb4a0bf2..6f1c22ffc 100755 --- a/src/apps/components/exit/runtime.ts +++ b/src/apps/components/exit/runtime.ts @@ -1,6 +1,7 @@ import { AppProcess } from "$ts/apps/process"; import { Store } from "$ts/writable"; import type { AppProcessData } from "$types/app"; +import type { RenderArgs } from "$types/process"; import { ExitActions } from "./store"; import type { ExitAction } from "./types"; @@ -30,4 +31,8 @@ export class ExitRuntime extends AppProcess { option.alternateAction(this.userDaemon!); // Alternate: when shift key is pressed else option.action(this.userDaemon!); } + + render(args: RenderArgs) { + this.getBody().setAttribute("data-prefix", "apps.ExitApp"); + } } diff --git a/src/apps/components/exit/store.ts b/src/apps/components/exit/store.ts index 422d44aad..4166ff25c 100755 --- a/src/apps/components/exit/store.ts +++ b/src/apps/components/exit/store.ts @@ -4,18 +4,18 @@ import type { ExitAction } from "./types"; export const ExitActions: Record = { restart: { action: (daemon: UserDaemon) => daemon.restart(), - caption: "Restart", + caption: "%exitActions.restart%", icon: "RestartIcon", }, shutdown: { action: (daemon: UserDaemon) => daemon.shutdown(), - caption: "Shut down", + caption: "%exitActions.shutdown%", icon: "ShutdownIcon", }, logoff: { action: (daemon: UserDaemon) => daemon.logoff(), alternateAction: (daemon: UserDaemon) => daemon.logoffSafeMode(), - caption: "Log off", + caption: "%exitActions.logoff%", icon: "LogoutIcon", }, }; diff --git a/src/lang/en.json b/src/lang/en.json index 3336887a1..94b1c2841 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -337,6 +337,18 @@ "simple": "Simple", "advanced": "Advanced" } + }, + "ExitApp": { + "_name": "Exit", + "header": { + "title": "Exit ArcOS", + "message": "What's your escape route?" + }, + "exitActions": { + "restart": "Restart", + "shutdown": "Shutdown", + "logoff": "Log off" + } } }, "general": { @@ -357,7 +369,8 @@ "close": "Close", "yes": "Yes", "no": "No", - "none": "None" + "none": "None", + "confirm": "Confirm" }, "appOrigins": { "builtin": "Built-in", From 35909c5049790a2e8520a8bd9187cfe7984f622b Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 29 Sep 2025 23:04:55 +0200 Subject: [PATCH 12/17] Translate FirstRun --- .../ChooseProfilePicture.svelte | 6 +- .../firstrun/ChooseProfilePicture/metadata.ts | 2 +- src/apps/components/firstrun/FirstRun.svelte | 4 +- src/apps/components/firstrun/FirstRun.ts | 2 +- .../firstrun/FirstRun/Page/Finish.svelte | 4 +- .../FirstRun/Page/ProfilePicture.svelte | 4 +- .../firstrun/FirstRun/Page/Style.svelte | 4 +- .../firstrun/FirstRun/Page/ThirdParty.svelte | 4 +- .../firstrun/FirstRun/Page/Welcome.svelte | 4 +- src/apps/components/firstrun/runtime.ts | 4 +- src/apps/components/firstrun/store.ts | 48 ++++++------- src/apps/user/filemanager/runtime.ts | 4 +- src/lang/en.json | 68 +++++++++++++++++++ src/ts/kernel/mods/i18n/index.ts | 7 +- src/ts/server/user/store.ts | 30 ++++---- 15 files changed, 133 insertions(+), 62 deletions(-) diff --git a/src/apps/components/firstrun/ChooseProfilePicture/ChooseProfilePicture.svelte b/src/apps/components/firstrun/ChooseProfilePicture/ChooseProfilePicture.svelte index c3c3a2765..ac7ef6c77 100755 --- a/src/apps/components/firstrun/ChooseProfilePicture/ChooseProfilePicture.svelte +++ b/src/apps/components/firstrun/ChooseProfilePicture/ChooseProfilePicture.svelte @@ -8,8 +8,8 @@
-

Choose profile picture

-

What do you want to be?

+

%apps.FirstRun.ChooseProfilePicture.title%

+

%apps.FirstRun.ChooseProfilePicture.subtitle%

{#each Object.values(ProfilePictures) as pfp, i}
- +
diff --git a/src/apps/components/firstrun/ChooseProfilePicture/metadata.ts b/src/apps/components/firstrun/ChooseProfilePicture/metadata.ts index 6323cbf49..7f9f09140 100755 --- a/src/apps/components/firstrun/ChooseProfilePicture/metadata.ts +++ b/src/apps/components/firstrun/ChooseProfilePicture/metadata.ts @@ -4,7 +4,7 @@ import ChooseProfilePicture from "./ChooseProfilePicture.svelte"; export const ChooseProfilePictureApp: App = { metadata: { - name: "Choose Profile Picture", + name: "%apps.FirstRun.ChooseProfilePicture._name%", author: "Izaak Kuipers", version: "1.0.0", icon: "AccountIcon", diff --git a/src/apps/components/firstrun/FirstRun.svelte b/src/apps/components/firstrun/FirstRun.svelte index f61f2cee3..37b7ac7fe 100755 --- a/src/apps/components/firstrun/FirstRun.svelte +++ b/src/apps/components/firstrun/FirstRun.svelte @@ -15,7 +15,7 @@ }); -
+
{#if Component} {:else} @@ -24,7 +24,7 @@
{/if}
-
+
{#each $currentPage?.actions.left as button} - + +
diff --git a/src/apps/components/fsnewfolder/FsNewFolder.ts b/src/apps/components/fsnewfolder/FsNewFolder.ts index 5a525b74d..65eb3f513 100644 --- a/src/apps/components/fsnewfolder/FsNewFolder.ts +++ b/src/apps/components/fsnewfolder/FsNewFolder.ts @@ -5,7 +5,7 @@ import { NewFolderRuntime } from "./runtime"; export const FsNewFolderApp: App = { metadata: { - name: "New Folder", + name: "%apps.FsNewFolder.title%", version: "4.0.0", author: "Izaak Kuipers", icon: "ComponentIcon", diff --git a/src/apps/components/fsnewfolder/NewFolder.svelte b/src/apps/components/fsnewfolder/NewFolder.svelte index 855ba6c65..2e4e0f89a 100755 --- a/src/apps/components/fsnewfolder/NewFolder.svelte +++ b/src/apps/components/fsnewfolder/NewFolder.svelte @@ -8,12 +8,12 @@
-

New folder

-

Think of a wonderful name for this new folder:

+

%apps.FsNewFolder.title%

+

%apps.FsNewFile.subtitle%

- - + +
diff --git a/src/apps/components/fsprogress/FsProgress.ts b/src/apps/components/fsprogress/FsProgress.ts index 5deb6fef8..dcb11d3f4 100644 --- a/src/apps/components/fsprogress/FsProgress.ts +++ b/src/apps/components/fsprogress/FsProgress.ts @@ -6,7 +6,7 @@ import { FsProgressRuntime } from "./runtime"; export const FsProgressApp: App = { metadata: { name: "FsProgress", - author: "The ArcOS Team", + author: "%general.ArcOSTeam%", version: "1.0.0", icon: "ComponentIcon", appGroup: "components", diff --git a/src/apps/components/fsprogress/FsProgress/Bottom.svelte b/src/apps/components/fsprogress/FsProgress/Bottom.svelte index f1bd7b304..b1d46784c 100755 --- a/src/apps/components/fsprogress/FsProgress/Bottom.svelte +++ b/src/apps/components/fsprogress/FsProgress/Bottom.svelte @@ -20,11 +20,11 @@ {#if $Progress.max > 0}

{#if $Progress.type == "quantity"} - {$Progress.done} / {$Progress.max} done + %apps.FsProgress.quantity({$Progress.done}::{$Progress.max})% {:else if $Progress.type == "size"} - {formatBytes($Progress.done)} / {formatBytes($Progress.max)} done + %apps.FsProgress.size({formatBytes($Progress.done)}::{formatBytes($Progress.max)})% {/if}

{/if} - +
diff --git a/src/lang/en.json b/src/lang/en.json index 26f1be8df..4be2f13f9 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -396,6 +396,20 @@ "darkMode": "Dark mode", "lightMode": "Light mode" } + }, + "FsNewFile": { + "title": "New file", + "subtitle": "Think of a wonderful name for this new file:", + "create": "Create" + }, + "FsNewFolder": { + "title": "New folder", + "subtitle": "Think of a wonderful name for this new folder:", + "create": "Create" + }, + "FsProgress": { + "quantity": "{{0}} / {{1}} done", + "size": "{{0}} / {{1}} done" } }, "general": { @@ -417,7 +431,8 @@ "yes": "Yes", "no": "No", "none": "None", - "confirm": "Confirm" + "confirm": "Confirm", + "ArcOSTeam": "The ArcOS Team" }, "appOrigins": { "builtin": "Built-in", From 7730d4de906a5736b2cdae6764a1a787a8ab194f Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Tue, 30 Sep 2025 17:26:13 +0200 Subject: [PATCH 14/17] Translate FsProgressFail --- src/apps/components/fsprogressfail/FsProgressFail.svelte | 4 ++-- src/lang/en.json | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/apps/components/fsprogressfail/FsProgressFail.svelte b/src/apps/components/fsprogressfail/FsProgressFail.svelte index 1065a447b..24698b5ca 100755 --- a/src/apps/components/fsprogressfail/FsProgressFail.svelte +++ b/src/apps/components/fsprogressfail/FsProgressFail.svelte @@ -8,7 +8,7 @@

{title}

-

This file operation encountered an error.

+

%apps.FsProgressFail.subtitle%

{#each errors as error} @@ -18,4 +18,4 @@
{/each}
- + diff --git a/src/lang/en.json b/src/lang/en.json index 4be2f13f9..036924f5d 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -410,6 +410,9 @@ "FsProgress": { "quantity": "{{0}} / {{1}} done", "size": "{{0}} / {{1}} done" + }, + "FsProgressFail": { + "subtitle": "This file operation encountered an error" } }, "general": { From bf8af4f5ebc593a0b21a8925dc25e1ae8de85b9b Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Tue, 30 Sep 2025 17:55:50 +0200 Subject: [PATCH 15/17] Translate FsRenameItem, GlobalLoadIndicator, IconEditDialog --- .../components/fsrenameitem/RenameItem.svelte | 8 ++--- .../components/globalloadindicator/runtime.ts | 2 +- .../iconeditdialog/IconEditDialog.svelte | 8 ++--- .../iconeditdialog/IconEditDialog.ts | 2 +- .../IconEditDialog/AppType.svelte | 4 +-- .../IconEditDialog/BuiltinType.svelte | 4 +-- .../IconEditDialog/FileType.svelte | 6 ++-- .../IconEditDialog/ModeToggle.svelte | 6 ++-- src/apps/components/iconeditdialog/runtime.ts | 4 +++ src/lang/en.json | 32 ++++++++++++++++++- 10 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/apps/components/fsrenameitem/RenameItem.svelte b/src/apps/components/fsrenameitem/RenameItem.svelte index d2e6e43be..404271bb0 100755 --- a/src/apps/components/fsrenameitem/RenameItem.svelte +++ b/src/apps/components/fsrenameitem/RenameItem.svelte @@ -8,12 +8,12 @@
-

Rename file or folder

-

Enter a new name for the item:

+

%apps.FsRenameItem.title%

+

%apps.FsRenameItem.subtitle%

- - + +
diff --git a/src/apps/components/globalloadindicator/runtime.ts b/src/apps/components/globalloadindicator/runtime.ts index 74c3ce89e..31cd81a09 100755 --- a/src/apps/components/globalloadindicator/runtime.ts +++ b/src/apps/components/globalloadindicator/runtime.ts @@ -3,7 +3,7 @@ import { Store } from "$ts/writable"; import type { AppProcessData } from "$types/app"; export class GlobalLoadIndicatorRuntime extends AppProcess { - caption = Store("Just a moment..."); + caption = Store("%general.genericStatus%"); //#region LIFECYCLE diff --git a/src/apps/components/iconeditdialog/IconEditDialog.svelte b/src/apps/components/iconeditdialog/IconEditDialog.svelte index 6705959d6..7128fa1b0 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog.svelte +++ b/src/apps/components/iconeditdialog/IconEditDialog.svelte @@ -11,7 +11,7 @@
-

Change {id}

+

%title({id})%

{#if $type === "@fs"} @@ -23,10 +23,10 @@
- - + +
diff --git a/src/apps/components/iconeditdialog/IconEditDialog.ts b/src/apps/components/iconeditdialog/IconEditDialog.ts index cdb9ab822..d43ff3180 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog.ts +++ b/src/apps/components/iconeditdialog/IconEditDialog.ts @@ -5,7 +5,7 @@ import { IconEditDialogRuntime } from "./runtime"; export const IconEditDialogApp: App = { metadata: { - name: "Change Icon", + name: "%apps.IconEditDialog._name%", version: "1.0.0", author: "Izaak Kuipers", icon: "ComponentIcon", diff --git a/src/apps/components/iconeditdialog/IconEditDialog/AppType.svelte b/src/apps/components/iconeditdialog/IconEditDialog/AppType.svelte index 4fbf8680a..5e4b572bc 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog/AppType.svelte +++ b/src/apps/components/iconeditdialog/IconEditDialog/AppType.svelte @@ -7,11 +7,11 @@
-

App:

+

%appType.title%

diff --git a/src/apps/components/iconeditdialog/IconEditDialog/BuiltinType.svelte b/src/apps/components/iconeditdialog/IconEditDialog/BuiltinType.svelte index 40dc5c730..d9ac66849 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog/BuiltinType.svelte +++ b/src/apps/components/iconeditdialog/IconEditDialog/BuiltinType.svelte @@ -14,7 +14,7 @@
-

Icon ID:

+

%builtinType.title%

{#if $values[$type]} @@ -22,6 +22,6 @@ {/if} {$values[$type]}
- +
diff --git a/src/apps/components/iconeditdialog/IconEditDialog/FileType.svelte b/src/apps/components/iconeditdialog/IconEditDialog/FileType.svelte index 2627c90a3..6109cf24f 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog/FileType.svelte +++ b/src/apps/components/iconeditdialog/IconEditDialog/FileType.svelte @@ -8,7 +8,7 @@ async function browse() { const [path] = await process.userDaemon!.LoadSaveDialog({ - title: "Choose an icon to load", + title: "%apps.IconEditDialog.fileType.loadSaveTitle%", extensions: [".svg", ".png", ".jpg", ".bmp", ".gif", ".jpeg"], icon: UploadIcon, startDir: UserPaths.Pictures, @@ -19,9 +19,9 @@
-

File path:

+

%fileType.title%

- +
diff --git a/src/apps/components/iconeditdialog/IconEditDialog/ModeToggle.svelte b/src/apps/components/iconeditdialog/IconEditDialog/ModeToggle.svelte index 821ef4488..00aee12cf 100644 --- a/src/apps/components/iconeditdialog/IconEditDialog/ModeToggle.svelte +++ b/src/apps/components/iconeditdialog/IconEditDialog/ModeToggle.svelte @@ -6,7 +6,7 @@
- - - + + +
diff --git a/src/apps/components/iconeditdialog/runtime.ts b/src/apps/components/iconeditdialog/runtime.ts index b33dfd474..ed2b9e8ad 100644 --- a/src/apps/components/iconeditdialog/runtime.ts +++ b/src/apps/components/iconeditdialog/runtime.ts @@ -31,6 +31,10 @@ export class IconEditDialogRuntime extends AppProcess { this.values.subscribe((v) => this.updateCurrentIcon(this.type(), v)); } + async render() { + this.getBody().setAttribute("data-prefix", "apps.IconEditDialog"); + } + //#endregion async updateCurrentIcon(type: string = this.type(), values: Record = this.values()) { diff --git a/src/lang/en.json b/src/lang/en.json index 036924f5d..1845e4772 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -413,6 +413,34 @@ }, "FsProgressFail": { "subtitle": "This file operation encountered an error" + }, + "FsRenameItem": { + "title": "Rename file or folder", + "subtitle": "Enter a new name for the item:", + "rename": "Rename" + }, + "IconEditDialog": { + "_name": "Change Icon", + "title": "Change {{0}}", + "modeToggle": { + "app": "App", + "fs": "File", + "builtin": "Built-in" + }, + "fileType": { + "loadSaveTitle": "Choose an icon to load", + "title": "File path:", + "buttonTitle": "Choose file" + }, + "appType": { + "title": "App:", + "option": "{{0}} by {{1}}" + }, + "builtinType": { + "title": "Icon ID:", + "buttonTitle": "Choose icon" + }, + "default": "Default" } }, "general": { @@ -435,7 +463,9 @@ "no": "No", "none": "None", "confirm": "Confirm", - "ArcOSTeam": "The ArcOS Team" + "ArcOSTeam": "The ArcOS Team", + "genericStatus": "Just a moment...", + "save": "Save" }, "appOrigins": { "builtin": "Built-in", From bbe5c9252fa5e026241117f8a123b4204e6c8382 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Tue, 30 Sep 2025 21:42:14 +0200 Subject: [PATCH 16/17] Translate IconPicker, ItemInfo, MessageComposer, and assoc stuff --- .../components/iconpicker/IconPicker.svelte | 8 +- src/apps/components/iconpicker/IconPicker.ts | 2 +- .../iconpicker/IconPicker/Header.svelte | 2 +- src/apps/components/iconpicker/runtime.ts | 4 + src/apps/components/iconpicker/store.ts | 16 +-- src/apps/components/iteminfo/ItemInfo.ts | 2 +- .../iteminfo/ItemInfo/Actions.svelte | 6 +- .../iteminfo/ItemInfo/Header.svelte | 4 +- .../iteminfo/ItemInfo/Location.svelte | 10 +- .../components/iteminfo/ItemInfo/Meta.svelte | 10 +- src/apps/components/iteminfo/runtime.ts | 2 + .../messagecomposer/MessageComposer.ts | 2 +- .../MessageComposer/ActionBar.svelte | 14 +- .../MessageComposer/AttachmentBar.svelte | 4 +- .../MessageComposer/SubjectField.svelte | 2 +- .../MessageComposer/ToField.svelte | 8 +- .../components/messagecomposer/runtime.ts | 31 ++-- .../FileManager/AddressBar/Address.svelte | 6 +- src/lang/en.json | 132 +++++++++++++++++- src/ts/server/user/assoc/store/audio.ts | 10 +- src/ts/server/user/assoc/store/image.ts | 18 +-- src/ts/server/user/assoc/store/index.ts | 68 ++++----- src/ts/server/user/assoc/store/video.ts | 8 +- 23 files changed, 255 insertions(+), 114 deletions(-) diff --git a/src/apps/components/iconpicker/IconPicker.svelte b/src/apps/components/iconpicker/IconPicker.svelte index 25f1316a5..fbf124d2e 100755 --- a/src/apps/components/iconpicker/IconPicker.svelte +++ b/src/apps/components/iconpicker/IconPicker.svelte @@ -28,12 +28,12 @@ %reset% - +
- - + +
diff --git a/src/apps/components/iconpicker/IconPicker.ts b/src/apps/components/iconpicker/IconPicker.ts index a1aecf36f..3624c525a 100644 --- a/src/apps/components/iconpicker/IconPicker.ts +++ b/src/apps/components/iconpicker/IconPicker.ts @@ -5,7 +5,7 @@ import { IconPickerRuntime } from "./runtime"; export const IconPickerApp: App = { metadata: { - name: "Icon Picker", + name: "%apps.IconPicker._name%", version: "3.0.0", author: "Izaak Kuipers", icon: "IconLibraryIcon", diff --git a/src/apps/components/iconpicker/IconPicker/Header.svelte b/src/apps/components/iconpicker/IconPicker/Header.svelte index f9abc8be2..2b8db9a85 100755 --- a/src/apps/components/iconpicker/IconPicker/Header.svelte +++ b/src/apps/components/iconpicker/IconPicker/Header.svelte @@ -6,7 +6,7 @@
-

Pick an icon for {forWhat}

+

%title({forWhat})%

{forWhat} diff --git a/src/apps/components/iconpicker/runtime.ts b/src/apps/components/iconpicker/runtime.ts index 75506f56e..3c788f590 100755 --- a/src/apps/components/iconpicker/runtime.ts +++ b/src/apps/components/iconpicker/runtime.ts @@ -40,6 +40,10 @@ export class IconPickerRuntime extends AppProcess { this.groups = iconService.getGroupedIcons(); } + async render() { + this.getBody().setAttribute("data-prefix", "apps.IconPicker"); + } + //#endregion async confirm() { diff --git a/src/apps/components/iconpicker/store.ts b/src/apps/components/iconpicker/store.ts index eb442f0f6..ea4e4b445 100755 --- a/src/apps/components/iconpicker/store.ts +++ b/src/apps/components/iconpicker/store.ts @@ -1,10 +1,10 @@ export const ICON_GROUP_CAPTIONS = { - Branding: "ArcOS logos", - General: "General icons", - Apps: "Application icons", - Filesystem: "Filesystem-related icons", - Power: "Power icons", - Dialog: "Dialog icons", - Status: "Status indicators", - Mimetypes: "File mimetypes", + Branding: "%groups.Branding%", + General: "%groups.General%", + Apps: "%groups.Apps%", + Filesystem: "%groups.Filesystem%", + Power: "%groups.Power%", + Dialog: "%groups.Dialog%", + Status: "%groups.Status%", + Mimetypes: "%groups.Mimetypes%", }; diff --git a/src/apps/components/iteminfo/ItemInfo.ts b/src/apps/components/iteminfo/ItemInfo.ts index 935017ed1..2f76b26c1 100644 --- a/src/apps/components/iteminfo/ItemInfo.ts +++ b/src/apps/components/iteminfo/ItemInfo.ts @@ -5,7 +5,7 @@ import { ItemInfoRuntime } from "./runtime"; export const ItemInfoApp: App = { metadata: { - name: "Item Info", + name: "%apps.ItemInfo._name%", version: "1.0.0", author: "Izaak Kuipers", icon: "ComponentIcon", diff --git a/src/apps/components/iteminfo/ItemInfo/Actions.svelte b/src/apps/components/iteminfo/ItemInfo/Actions.svelte index 44e2723cb..0ccc293fb 100755 --- a/src/apps/components/iteminfo/ItemInfo/Actions.svelte +++ b/src/apps/components/iteminfo/ItemInfo/Actions.svelte @@ -14,10 +14,10 @@ {/if} - - + + diff --git a/src/apps/components/iteminfo/ItemInfo/Header.svelte b/src/apps/components/iteminfo/ItemInfo/Header.svelte index 87fa4ccda..6d3503d6b 100755 --- a/src/apps/components/iteminfo/ItemInfo/Header.svelte +++ b/src/apps/components/iteminfo/ItemInfo/Header.svelte @@ -19,10 +19,10 @@

{$info.name || $info.location.parent || $info.location.drive}

{#if $info.name && ($info.location.parent || $info.location.drive)} -

in {$info.location.parent || $info.location.drive}

+

%subtitle({$info.location.parent || $info.location.drive})%

{/if}
{#if $info.name} - + {/if}
diff --git a/src/apps/components/iteminfo/ItemInfo/Location.svelte b/src/apps/components/iteminfo/ItemInfo/Location.svelte index d414127b2..55b30919a 100755 --- a/src/apps/components/iteminfo/ItemInfo/Location.svelte +++ b/src/apps/components/iteminfo/ItemInfo/Location.svelte @@ -11,21 +11,21 @@ - + {$info.location.fullPath} - + {$info.location.extension ? "." + $info.location.extension : "-"} - + {$info.location.parent || "-"} - + {$info.location.drive || "-"} - + {$info.location.driveFs || "GFS"} diff --git a/src/apps/components/iteminfo/ItemInfo/Meta.svelte b/src/apps/components/iteminfo/ItemInfo/Meta.svelte index 1049d79cf..4b152c053 100755 --- a/src/apps/components/iteminfo/ItemInfo/Meta.svelte +++ b/src/apps/components/iteminfo/ItemInfo/Meta.svelte @@ -24,21 +24,21 @@ - + {$info.name ? $info.meta.sort : "drive"} - + {$info.meta.mimetype || "-"} - + {$info.meta.size ? formatBytes($info.meta.size) : "-"} - + {created} - + {modified} diff --git a/src/apps/components/iteminfo/runtime.ts b/src/apps/components/iteminfo/runtime.ts index a176df840..2e9d72154 100755 --- a/src/apps/components/iteminfo/runtime.ts +++ b/src/apps/components/iteminfo/runtime.ts @@ -27,6 +27,8 @@ export class ItemInfoRuntime extends AppProcess { } async render({ path, file }: RenderArgs) { + this.getBody().setAttribute("data-prefix", "apps.ItemInfo"); + file = file as FileEntry | FolderEntry; try { diff --git a/src/apps/components/messagecomposer/MessageComposer.ts b/src/apps/components/messagecomposer/MessageComposer.ts index cffdc5e56..d348f76a3 100644 --- a/src/apps/components/messagecomposer/MessageComposer.ts +++ b/src/apps/components/messagecomposer/MessageComposer.ts @@ -5,7 +5,7 @@ import { MessageComposerRuntime } from "./runtime"; export const MessageComposerApp: App = { metadata: { - name: "New Message", + name: "%apps.MessageComposer._name%", author: "Izaak Kuipers", version: "1.0.0", icon: "MessagingIcon", diff --git a/src/apps/components/messagecomposer/MessageComposer/ActionBar.svelte b/src/apps/components/messagecomposer/MessageComposer/ActionBar.svelte index aa9b2ec37..580c2ebbd 100755 --- a/src/apps/components/messagecomposer/MessageComposer/ActionBar.svelte +++ b/src/apps/components/messagecomposer/MessageComposer/ActionBar.svelte @@ -9,31 +9,31 @@
-

Body

+

%actionBar.body%

{formatBytes($body.length)}

-

Attachments

+

%actionBar.attachments%

{formatBytes($attachments.map((a) => a.data.size).reduce((partialSum, a) => partialSum + a, 0))}

%actionBar.send%
diff --git a/src/apps/components/messagecomposer/MessageComposer/AttachmentBar.svelte b/src/apps/components/messagecomposer/MessageComposer/AttachmentBar.svelte index 6560312e6..3fec16b12 100755 --- a/src/apps/components/messagecomposer/MessageComposer/AttachmentBar.svelte +++ b/src/apps/components/messagecomposer/MessageComposer/AttachmentBar.svelte @@ -19,10 +19,10 @@ ({formatBytes(attachment.data.size)})
diff --git a/src/apps/components/messagecomposer/MessageComposer/SubjectField.svelte b/src/apps/components/messagecomposer/MessageComposer/SubjectField.svelte index 1c4c0cf0c..5aaec102f 100755 --- a/src/apps/components/messagecomposer/MessageComposer/SubjectField.svelte +++ b/src/apps/components/messagecomposer/MessageComposer/SubjectField.svelte @@ -6,7 +6,7 @@
-

Subject:

+

%subjectField.subject%

diff --git a/src/apps/components/messagecomposer/MessageComposer/ToField.svelte b/src/apps/components/messagecomposer/MessageComposer/ToField.svelte index d39bb7d75..ef1dcfb81 100755 --- a/src/apps/components/messagecomposer/MessageComposer/ToField.svelte +++ b/src/apps/components/messagecomposer/MessageComposer/ToField.svelte @@ -26,7 +26,7 @@
-

To:

+

%toField.to%

{#each $recipients as recipient}
@@ -34,12 +34,12 @@
{/each} - +
diff --git a/src/apps/components/messagecomposer/runtime.ts b/src/apps/components/messagecomposer/runtime.ts index 2680655aa..bc2d7a476 100755 --- a/src/apps/components/messagecomposer/runtime.ts +++ b/src/apps/components/messagecomposer/runtime.ts @@ -49,8 +49,8 @@ export class MessageComposerRuntime extends AppProcess { const prog = await this.userDaemon?.FileProgress( { type: "none", - caption: "Sending message", - subtitle: "Preparing...", + caption: "%apps.MessageComposer.sendProg.caption%", + subtitle: "%apps.MessageComposer.sendProg.subtitleInitial%", icon: "MessagingIcon", }, this.pid @@ -67,7 +67,7 @@ export class MessageComposerRuntime extends AppProcess { prog?.setType("size"); prog?.setMax(progress.max); prog?.setDone(progress.value); - prog?.updSub("Uploading..."); + prog?.updSub("%apps.MessageComposer.sendProg.subtitleUploading%"); } ); @@ -83,11 +83,11 @@ export class MessageComposerRuntime extends AppProcess { if (!this.isModified()) return this.closeWindow(); MessageBox( { - title: "Discard message?", - message: "Are you sure you want to discard this message? This cannot be undone.", + title: "%apps.MessageComposer.discardMessage.title%", + message: "%apps.MessageComposer.discardMessage.title%", buttons: [ - { caption: "Cancel", action: () => {} }, - { caption: "Discard", action: () => this.closeWindow(), suggested: true }, + { caption: "%general.cancel%", action: () => {} }, + { caption: "%general.discard%", action: () => this.closeWindow(), suggested: true }, ], image: "WarningIcon", sound: "arcos.dialog.warning", @@ -101,12 +101,11 @@ export class MessageComposerRuntime extends AppProcess { this.sending.set(false); MessageBox( { - title: "Failed to send message", - message: - "ArcOS failed to send the message! It might be too large, or none of the recipients exist. Please check the recipients or try shrinking it down, and then resend it. If it still doesn't work, contact an ArcOS administrator.", + title: "%apps.MessageComposer.sendFailed.title%", + message: "%apps.MessageComposer.sendFailed.message%", image: "WarningIcon", sound: "arcos.dialog.warning", - buttons: [{ caption: "Okay", action: () => {}, suggested: true }], + buttons: [{ caption: "%general.okay%", action: () => {}, suggested: true }], }, this.pid, true @@ -119,7 +118,7 @@ export class MessageComposerRuntime extends AppProcess { async addAttachment() { const attachments: Attachment[] = []; const paths = await this.userDaemon!.LoadSaveDialog({ - title: "Choose one or more files to attach", + title: "%apps.MessageComposer.addAttachment.title%", icon: "UploadIcon", startDir: UserPaths.Documents, multiple: true, @@ -129,7 +128,7 @@ export class MessageComposerRuntime extends AppProcess { { max: 100, type: "none", - caption: "Just a moment...", + caption: "%general.genericStatus%", icon: "MemoryIcon", }, this.pid @@ -143,7 +142,11 @@ export class MessageComposerRuntime extends AppProcess { try { const contents = await this.fs.readFile(path, (progress) => { prog.show(); - prog.updateCaption(progress.what ? `Reading ${progress.what}` : "Reading file..."); + prog.updateCaption( + progress.what + ? `%apps.MessageComposer.fileProgress.readingFileKnown(${progress.what})%` + : "%apps.MessageComposer.fileProgress.readingFileUnknown%" + ); prog.updSub(path); prog.setType("size"); prog.setDone(0); diff --git a/src/apps/user/filemanager/FileManager/AddressBar/Address.svelte b/src/apps/user/filemanager/FileManager/AddressBar/Address.svelte index 3b93a9eca..7b076459e 100755 --- a/src/apps/user/filemanager/FileManager/AddressBar/Address.svelte +++ b/src/apps/user/filemanager/FileManager/AddressBar/Address.svelte @@ -37,7 +37,11 @@
- {$virtual ? $virtual.name : driveLetter || driveLabel} + {#if $virtual} + {$virtual.name} + {:else} + {driveLetter || driveLabel} + {/if}
{#if name && !$virtual}
diff --git a/src/lang/en.json b/src/lang/en.json index 1845e4772..66b2d3665 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -152,7 +152,7 @@ "_name": "App Info", "noTargetApp": { "title": "App not found", - "message": "AppInfo couldn't find any information about \"{{0}}\". Is it installed?" + "message": "AppInfo couldn't find any information about \"{{0}}\" Is it installed?" }, "killAll": { "what": "ArcOS needs your permission to kill all instances of an app" @@ -441,12 +441,94 @@ "buttonTitle": "Choose icon" }, "default": "Default" + }, + "IconPicker": { + "_name": "Icon Picker", + "groups": { + "Branding": "ArcOS logos", + "General": "General icons", + "Apps": "Application icons", + "Filesystem": "Filesystem-related icons", + "Power": "Power icons", + "Dialog": "Dialog icons", + "Status": "Status indicators", + "Mimetypes": "File mimetypes" + }, + "random": "Random", + "choose": "Choose", + "reset": "Reset", + "title": "Pick an icon for {{0}}" + }, + "ItemInfo": { + "_name": "Item Info", + "subtitle": "in {{0}}", + "renameItem": "Rename item", + "location": { + "fullPath": "Full Path", + "extension": "Extension", + "parent": "Parent", + "drive": "Drive", + "filesystem": "Filesystem" + }, + "meta": { + "sort": "Sort", + "type": "Type", + "size": "Size", + "created": "Created", + "modified": "Modified" + }, + "actions": { + "editShortcut": "Edit shortcut..." + } + }, + "MessageComposer": { + "_name": "New Message", + "sendProg": { + "caption": "Sending message", + "subtitleInitial": "Preparing", + "subtitleUploading": "Uploading" + }, + "discardMessage": { + "title": "Discard message?", + "message": "Are you sure you want to discard this message? This cannot be undone." + }, + "sendFailed": { + "title": "Failed to send message", + "message": "ArcOS failed to send the message! It might be too large, or none of the recipients exist. Please check the recipients or try shrinking it down, and then resend it. If it still doesn't work, contact an ArcOS administrator." + }, + "addAttachment": { + "title": "Choose one or more files to attach", + "fileProgress": { + "caption": "Just a moment...", + "readingFileKnown": "Reading {{0}}", + "readingFileUnknown": "Readon file..." + } + }, + "actionBar": { + "body": "Body", + "attachments": "Attachments", + "addAttachment": "Add attachment", + "discardMessage": "Discard message", + "send": "Send" + }, + "attachmentBar": { + "removeAttachment": "Remove attachment" + }, + "subjectField": { + "subject": "Subject:" + }, + "toField": { + "to": "To:", + "removeRecipient": "Remove {{0}}", + "enterUsername": "Enter username" + } } }, "general": { "betaPill": "BETA", "cancel": "Cancel", "okay": "Okay", + "open": "Open", "previous": "Previous", "next": "Next", "continue": "Continue", @@ -465,7 +547,8 @@ "confirm": "Confirm", "ArcOSTeam": "The ArcOS Team", "genericStatus": "Just a moment...", - "save": "Save" + "save": "Save", + "discard": "Discard" }, "appOrigins": { "builtin": "Built-in", @@ -493,5 +576,50 @@ "Configuration": "Configuration", "AppShortcuts": "Application Shortcuts", "AppRepository": "Application Repository" + }, + "associations": { + "tpa": "Third-party app file", + "tpab": "Third-party app binary", + "json": "JSON file", + "pdf": "PDF document", + "svg": "SVG image", + "zip": "Archive", + "txt": "Text file", + "arctermConf": "ArcTerm configuration", + "arcterm": "ArcTerm script (unsupported)", + "arctheme": "ArcOS theme", + "md": "Markdown document", + "html": "HTML document", + "htm": "HTML document", + "js": "JS script", + "dTs": "Type definitions", + "ts": "TS script", + "mjs": "JS module", + "xml": "XML file", + "arcpl": "ArcOS playlist", + "arclnk": "Shortcut", + "arc": "ArcOS package", + "msg": "ArcOS message", + "css": "CSS file", + "mig": "Migration status", + "lock": "Migration lockfile", + "RegisteredVersion": "Version registration", + "exe": "Windows executable (unsupported)", + "msi": "Windows installer (unsupported)", + "com": "MS-DOS executable (unsupported)", + "img": "Floppy image (unsupported)", + "iso": "CD-ROM image (unsupported)", + "osl": "OriginOS script (unsupported)", + "wasm": "WebAssembly (unsupported", + "bin": "Binary (unsupported)", + "mp3": "Audio file", + "opus": "Audio file", + "wav": "Audio file", + "m4a": "Audio file", + "flac": "Audio file", + "imageFile": "Image file", + "audioFile": "Audio file", + "videoFile": "Video file", + "shortcut": "Shortcut" } } diff --git a/src/ts/server/user/assoc/store/audio.ts b/src/ts/server/user/assoc/store/audio.ts index 05ff31459..0ab8d4080 100755 --- a/src/ts/server/user/assoc/store/audio.ts +++ b/src/ts/server/user/assoc/store/audio.ts @@ -2,23 +2,23 @@ import type { FileDefinition } from "$types/assoc"; export const AudioFileDefinitions: Record = { ".mp3": { - friendlyName: "Audio file", + friendlyName: "%associations.audioFile%", icon: "AudioMimeIcon", }, ".opus": { - friendlyName: "Audio file", + friendlyName: "%associations.audioFile%", icon: "AudioMimeIcon", }, ".wav": { - friendlyName: "Audio file", + friendlyName: "%associations.audioFile%", icon: "AudioMimeIcon", }, ".m4a": { - friendlyName: "Audio file", + friendlyName: "%associations.audioFile%", icon: "AudioMimeIcon", }, ".flac": { - friendlyName: "Audio file", + friendlyName: "%associations.audioFile%", icon: "AudioMimeIcon", }, }; diff --git a/src/ts/server/user/assoc/store/image.ts b/src/ts/server/user/assoc/store/image.ts index 2500be877..a1c84ff33 100755 --- a/src/ts/server/user/assoc/store/image.ts +++ b/src/ts/server/user/assoc/store/image.ts @@ -2,39 +2,39 @@ import type { FileDefinition } from "$types/assoc"; export const ImageFileDefinitions: Record = { ".png": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".jpg": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".gif": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".webp": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".ico": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".bmp": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".tif": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".tiff": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, ".jpeg": { - friendlyName: "Image file", + friendlyName: "%associations.imageFile%", icon: "ImageMimeIcon", }, }; diff --git a/src/ts/server/user/assoc/store/index.ts b/src/ts/server/user/assoc/store/index.ts index c505fcd2b..c8c03a0af 100755 --- a/src/ts/server/user/assoc/store/index.ts +++ b/src/ts/server/user/assoc/store/index.ts @@ -5,139 +5,139 @@ import { VideoFileDefinitions } from "./video"; export const DefaultFileDefinitions: Record = { ".tpa": { - friendlyName: "Third-party app file", + friendlyName: "%associations.tpa%", icon: "ArcAppMimeIcon", }, ".tpab": { - friendlyName: "Third-party app binary", + friendlyName: "%associations.tpab%", icon: "ArcAppMimeIcon", }, ".json": { - friendlyName: "JSON file", + friendlyName: "%associations.json%", icon: "JsonMimeIcon", }, ".pdf": { - friendlyName: "PDF document", + friendlyName: "%associations.pdf%", icon: "PdfMimeIcon", }, ".svg": { - friendlyName: "SVG image", + friendlyName: "%associations.svg%", icon: "SvgMimeIcon", }, ".zip": { - friendlyName: "Archive", + friendlyName: "%associations.zip%", icon: "CompressMimeIcon", }, ".txt": { - friendlyName: "Text file", + friendlyName: "%associations.txt%", icon: "TextMimeIcon", }, "arcterm.conf": { - friendlyName: "ArcTerm configuration", + friendlyName: "%associations.arctermConf%", icon: "TextMimeIcon", }, ".arcterm": { - friendlyName: "ArcTerm script (unsupported)", + friendlyName: "%associations.arcterm%", icon: "TextMimeIcon", }, ".arctheme": { - friendlyName: "ArcOS theme", + friendlyName: "%associations.arctheme%", icon: "TextMimeIcon", }, ".md": { - friendlyName: "Markdown document", + friendlyName: "%associations.md%", icon: "TextMimeIcon", }, ".html": { - friendlyName: "HTML document", + friendlyName: "%associations.html%", icon: "XmlMimeIcon", }, ".htm": { - friendlyName: "HTML document", + friendlyName: "%associations.htm%", icon: "XmlMimeIcon", }, ".js": { - friendlyName: "JS script", + friendlyName: "%associations.js%", icon: "JavascriptMimeIcon", }, ".d.ts": { - friendlyName: "Type definitions", + friendlyName: "%associations.dTs%", icon: "JavascriptMimeIcon", }, ".ts": { - friendlyName: "TS script", + friendlyName: "%associations.ts%", icon: "JavascriptMimeIcon", }, ".mjs": { - friendlyName: "JS module", + friendlyName: "%associations.js%", icon: "JavascriptMimeIcon", }, ".xml": { - friendlyName: "XML file", + friendlyName: "%associations.xml%", icon: "XmlMimeIcon", }, ".arcpl": { - friendlyName: "ArcOS playlist", + friendlyName: "%associations.arcpl%", icon: "PlaylistMimeIcon", }, ".arclnk": { - friendlyName: "Shortcut", + friendlyName: "%associations.shortcut%", icon: "ShortcutMimeIcon", }, ".arc": { - friendlyName: "ArcOS package", + friendlyName: "%associations.arc%", icon: "ArcAppMimeIcon", }, ".msg": { - friendlyName: "ArcOS message", + friendlyName: "%associations.msg%", icon: "MessagingIcon", }, ".css": { - friendlyName: "CSS file", + friendlyName: "%associations.css%", icon: "DefaultMimeIcon", }, ".mig": { - friendlyName: "Migration status", + friendlyName: "%associations.mig%", icon: "ComponentIcon", }, ".lock": { - friendlyName: "Migration lockfile", + friendlyName: "%associations.lock%", icon: "ComponentIcon", }, RegisteredVersion: { - friendlyName: "Version registration", + friendlyName: "%associations.RegisteredVersion%", icon: "ComponentIcon", }, ".exe": { - friendlyName: "Windows executable (unsupported)", + friendlyName: "%associations.exe%", icon: "UnknownFileIcon", }, ".msi": { - friendlyName: "Windows installer (unsupported)", + friendlyName: "%associations.msi%", icon: "UnknownFileIcon", }, ".com": { - friendlyName: "MS-DOS executable (unsupported)", + friendlyName: "%associations.com%", icon: "UnknownFileIcon", }, ".img": { - friendlyName: "Floppy image (unsupported)", + friendlyName: "%associations.img%", icon: "UnknownFileIcon", }, ".iso": { - friendlyName: "CD-ROM image (unsupported)", + friendlyName: "%associations.iso%", icon: "UnknownFileIcon", }, ".osl": { - friendlyName: "OriginOS script (unsupported)", + friendlyName: "%associations.osl%", icon: "UnknownFileIcon", }, ".wasm": { - friendlyName: "WebAssembly (unsupported", + friendlyName: "%associations.wasm%", icon: "UnknownFileIcon", }, ".bin": { - friendlyName: "Binary (unsupported)", + friendlyName: "%associations.bin%", icon: "UnknownFileIcon", }, ...AudioFileDefinitions, // AudioMimeIcon diff --git a/src/ts/server/user/assoc/store/video.ts b/src/ts/server/user/assoc/store/video.ts index 0b9e09c51..09abc5300 100755 --- a/src/ts/server/user/assoc/store/video.ts +++ b/src/ts/server/user/assoc/store/video.ts @@ -2,19 +2,19 @@ import type { FileDefinition } from "$types/assoc"; export const VideoFileDefinitions: Record = { ".mp4": { - friendlyName: "Video file", + friendlyName: "%associations.videoFile%", icon: "VideoMimeIcon", }, ".mkv": { - friendlyName: "Video file", + friendlyName: "%associations.videoFile%", icon: "VideoMimeIcon", }, ".mov": { - friendlyName: "Video file", + friendlyName: "%associations.videoFile%", icon: "VideoMimeIcon", }, ".avi": { - friendlyName: "Video file", + friendlyName: "%associations.videoFile%", icon: "VideoMimeIcon", }, }; From e6318278c108e62025a4d0441257c376d3695105 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Tue, 30 Sep 2025 23:03:44 +0200 Subject: [PATCH 17/17] translation stuff: engine broke yay --- .../components/messagecomposer/runtime.ts | 4 +++ .../multiupdategui/MultiUpdateGui.svelte | 32 +++++++++---------- .../multiupdategui/MultiUpdateGui.ts | 2 +- src/apps/components/multiupdategui/runtime.ts | 4 ++- src/lang/en.json | 24 ++++++++++++++ src/ts/kernel/mods/i18n/index.ts | 20 ++++++++++-- 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/apps/components/messagecomposer/runtime.ts b/src/apps/components/messagecomposer/runtime.ts index bc2d7a476..d22f4c5d4 100755 --- a/src/apps/components/messagecomposer/runtime.ts +++ b/src/apps/components/messagecomposer/runtime.ts @@ -38,6 +38,10 @@ export class MessageComposerRuntime extends AppProcess { this.setSource(__SOURCE__); } + async render() { + this.getBody().setAttribute("data-prefix", "apps.MessageComposer"); + } + //#endregion //#region SENDING diff --git a/src/apps/components/multiupdategui/MultiUpdateGui.svelte b/src/apps/components/multiupdategui/MultiUpdateGui.svelte index a3329cb3c..1172cb906 100755 --- a/src/apps/components/multiupdategui/MultiUpdateGui.svelte +++ b/src/apps/components/multiupdategui/MultiUpdateGui.svelte @@ -24,40 +24,40 @@ ? StoreItemIcon($currentPackage) : $done ? process.getIconCached("GoodStatusIcon") - : "UpdateIcon"} + : process.getIconCached("UpdateIcon")} alt="" />

{#if $working} {#if $currentPackage} - Updating {$currentPackage.pkg.name} + %title.updating({$currentPackage.pkg.name})% {:else} - Just a moment + %title.loading% {/if} {:else if $done} - Finished updating + %title.done% {:else} - Ready to update + %title.ready% {/if}

{#if $working} {#if $currentPackage && $currentPackage.user} - By + %generic.by% {:else} - Loading... + %generic.loading% {/if} {:else if $done} {#if $errored.length} {$errored.length} {Plural("error", $errored.length)} occured {:else} - All packages were updated. + %allPackagesUpdated% {/if} {:else} - Click Update to begin + %clickUpdate% {/if}

@@ -88,13 +88,13 @@

{#if item.type === "file"} - Writing file + %itemType.file% {:else if item.type === "mkdir"} - Creating directory + %itemType.mkdir% {:else if item.type === "registration"} - Registering + %itemType.registration% {:else} - Status + %itemType.generic% {/if}

{item.content}

@@ -111,10 +111,10 @@
{#if $showLog}%hideLog%{:else}%showLog%{/if} - + {#if $done}%finish%{:else}%startUpdate%{/if}
diff --git a/src/apps/components/multiupdategui/MultiUpdateGui.ts b/src/apps/components/multiupdategui/MultiUpdateGui.ts index 83c1a25db..651ed4b63 100644 --- a/src/apps/components/multiupdategui/MultiUpdateGui.ts +++ b/src/apps/components/multiupdategui/MultiUpdateGui.ts @@ -5,7 +5,7 @@ import { MultiUpdateGuiRuntime } from "./runtime"; export const MultiUpdateGuiApp: App = { metadata: { - name: "App Updater", + name: "%apps.MultiUpdateGui._name%", author: "Izaak Kuipers", version: "1.0.0", icon: "UpdateIcon", diff --git a/src/apps/components/multiupdategui/runtime.ts b/src/apps/components/multiupdategui/runtime.ts index 4b3a55b99..a64bacc43 100755 --- a/src/apps/components/multiupdategui/runtime.ts +++ b/src/apps/components/multiupdategui/runtime.ts @@ -50,6 +50,8 @@ export class MultiUpdateGuiRuntime extends AppProcess { } async render() { + this.getBody().setAttribute("data-prefix", "apps.MultiUpdateGui"); + this.win = this.getWindow(); if (this.updates.length > 15) { @@ -87,7 +89,7 @@ export class MultiUpdateGuiRuntime extends AppProcess { this.working.set(true); const elevated = await this.userDaemon!.manuallyElevate({ - what: `ArcOS needs your permission to update ${this.updates.length} ${Plural("app", this.updates.length)}.`, + what: `%apps.MultiUpdateGui.elevation(${this.updates.length}::${Plural("app", this.updates.length)})%`, title: this.app.data.metadata.name, description: this.app.data.metadata.author, image: "UpdateIcon", diff --git a/src/lang/en.json b/src/lang/en.json index 66b2d3665..5b014791f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -522,6 +522,28 @@ "removeRecipient": "Remove {{0}}", "enterUsername": "Enter username" } + }, + "MultiUpdateGui": { + "_name": "App Updater", + "elevation": "ArcOS needs your permission to update {{0}} {{1}}.", + "title": { + "updating": "Updating {{0}}", + "loading": "Just a moment", + "done": "Finished updating", + "ready": "Ready to update" + }, + "allPackagesUpdated": "All packages were updated.", + "clickUpdate": "Click Update to begin", + "itemType": { + "file": "Writing file", + "mkdir": "Creating directory", + "registration": "Registering", + "generic": "Status" + }, + "showLog": "Show log", + "hideLog": "Hide log", + "startUpdate": "Update", + "finish": "Finish" } }, "general": { @@ -548,6 +570,8 @@ "ArcOSTeam": "The ArcOS Team", "genericStatus": "Just a moment...", "save": "Save", + "by": "By", + "loading": "Loading...", "discard": "Discard" }, "appOrigins": { diff --git a/src/ts/kernel/mods/i18n/index.ts b/src/ts/kernel/mods/i18n/index.ts index 005411495..8929d36cf 100644 --- a/src/ts/kernel/mods/i18n/index.ts +++ b/src/ts/kernel/mods/i18n/index.ts @@ -7,7 +7,7 @@ import type { ConstructedWaveKernel, SystemDispatchType } from "$types/kernel"; export class I18n extends KernelModule { REGEX = /%(?[\w.=\-]+)(?:\((?(.*?))\)|)%/gm; - language: string = "nl"; + language: string = "en"; dispatch: SystemDispatchType; observer: MutationObserver | null = null; TARGET?: HTMLDivElement; @@ -79,7 +79,23 @@ export class I18n extends KernelModule { if (node.parentElement) { node.parentElement.setAttribute("data-i18n-original", str); - node.parentElement.innerHTML = resultString; + node.textContent = resultString; + + let raf: number; + + const x = () => { + if (node.isConnected) { + if (node.textContent !== resultString) { + this.replaceInNode(node); + } + + raf = requestAnimationFrame(x); + } else { + cancelAnimationFrame(raf); + } + }; + + raf = requestAnimationFrame(x); } } else if (node.nodeType === node.ELEMENT_NODE) { const el = node as HTMLElement;