Skip to content

Commit

Permalink
Settings: Allow to change UI language
Browse files Browse the repository at this point in the history
  • Loading branch information
benbucksch committed Jun 30, 2024
1 parent 357daa0 commit 597bf47
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 14 deletions.
37 changes: 36 additions & 1 deletion app/frontend/Settings/Global/Appearance.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,49 @@
<vbox class="appearance">
<DarkMode />
<HeaderGroupBox>
<hbox slot="header">{$t`Dark mode`}</hbox>
<DarkMode />
</HeaderGroupBox>

<HeaderGroupBox>
<hbox slot="header">{$t`Language`}</hbox>
<hbox>
<LanguageDropDown bind:language />
{#if language != getUILocale()}
<Button
label="Save and restart"
icon={RestartIcon}
on:click={onChangeLanguage}
classes="restart"
/>
{/if}
</hbox>
</HeaderGroupBox>
</vbox>

<script lang="ts">
import { getUILocale, saveUILocale, setUILocale, t } from "../../../l10n/l10n";
import { appGlobal } from "../../../logic/app";
import DarkMode from "./DarkMode.svelte";
import LanguageDropDown from "./LanguageDropDown.svelte";
import HeaderGroupBox from "../../Shared/HeaderGroupBox.svelte";
import Button from "../../Shared/Button.svelte";
import RestartIcon from "lucide-svelte/icons/rotate-ccw";
let language = getUILocale();
async function onChangeLanguage() {
saveUILocale(language);
setUILocale(language);
await appGlobal.remoteApp.restartApp(); // unfortunately needed for the strings in ts modules
}
</script>

<style>
.appearance :global(.darkmode svg) {
width: 32px;
height: 32px;
}
.appearance :global(.restart) {
margin-left: 24px;
}
</style>
17 changes: 17 additions & 0 deletions app/frontend/Settings/Global/LanguageDropDown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<select bind:value={language} on:change>
{#each Object.keys(localeNames) as langCode }
<option value={langCode}>
{localeNames[langCode]}
</option>
{/each}
</select>

<script lang="ts">
import { localeNames } from "../../../l10n/list";
/** 2-letter ISO language code. in/out */
export let language: string;
</script>

<style>
</style>
27 changes: 21 additions & 6 deletions app/l10n/l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ const languageMessages = {
uk: uk,
};

export function setLocale(lang: string) {
export function setUILocale(lang: string) {
// This function *must* be sync, for gt() to work in TS modules

// e.g. 'en' for 'en-US'
let lang2 = lang.substring(0, 2);

let availableLang = languageMessages[lang] ? lang :
let availableLang = cachedUILocale =
languageMessages[lang] ? lang :
languageMessages[lang2] ? lang2 :
sourceLocale;
let messages = languageMessages[availableLang];
Expand All @@ -63,7 +66,7 @@ export function setLocale(lang: string) {

/** Alternative implementation with dynamic loading of locale files. Not used. */
/*
export async function setLocaleAsync(lang: string) {
export async function setUILocaleAsync(lang: string) {
// e.g. 'en' for 'en-US'
let lang2 = lang.substring(0, 2);
Expand All @@ -79,10 +82,22 @@ export async function setLocaleAsync(lang: string) {
}
*/

let cachedUILocale: string;
/** @returns 2-letter ISO code for the language that we are using */
export function getUILocale(): string {
if (cachedUILocale) {
return cachedUILocale;
}
return localStorage.getItem("ui.locale") ?? navigator.language;
}

export function saveUILocale(language: string) {
localStorage.setItem("ui.locale", language);
}

let loadedLocale = false;
function initLocale() {
let lang = localStorage.getItem("locale.ui") ?? navigator.language;
setLocale(lang);
setUILocale(getUILocale());
}

/** Used in Svelte files, e.g.
Expand All @@ -93,7 +108,7 @@ export const t = derived(locale, () => gt);
* gt`Hello World!` or gt`Hello ${username}!` */
export function gt(descriptor, ...args) {
if (!loadedLocale) {
initLocale(); // setLocale() must be sync for this to work
initLocale(); // `setUILocale()` must be sync for this to work
loadedLocale = true;
}
return translateString(descriptor, ...args);
Expand Down
38 changes: 31 additions & 7 deletions app/l10n/list.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
export const sourceLocale = 'en';

/* List of languages, as shown in the settings UI.
* Do *not* translate these.
* (Otherwise, if you do cannot read Chinese, you cannot find German anymore.)
* Lang codes: <https://www.wikiwand.com/en/List_of_ISO_639_language_codes> */
export const localeNames = {
'ar': "Arabic / اَلْعَرَبِيَّةُ",
'cs': "Czech / Čeština",
'da': "Danish / Dansk",
'de': "German / Deutsch",
'el': "Greek / Ελληνικά",
'en': "English",
'es': "Spanish / Español",
'fi': "Finnish / Suomi",
'fr': "French / Français",
'it': "Italian / Italiano",
'ja': "Japanese / 日本語",
'nl': "Dutch / Nederlands",
'no': "Norwegian / Norsk",
'pl': "Polish / Polski",
'pt': "Portuguese / Português",
'ro': "Romanian / Românește",
'ru': "Russian / Русский язык",
'sv': "Swedish / Svenska",
'uk': "Ukranian / Українська",
'zh': "Simplified Chinese (Taiwan) / 简体中文",
};

/** List of available locales in this build.
* Must match ./locales/<locale>/* files and
* the imports in ./l10n.ts.
* Lang codes: <https://www.wikiwand.com/en/List_of_ISO_639_language_codes> */
export const locales = [
'ar', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr',
'it', 'ja', 'nl', 'no', 'pl', 'pt', 'ro', 'ru', 'sv', 'uk', 'zh'
];
export const sourceLocale = 'en';
* the imports in ./l10n.ts. */
export const locales = Object.keys(localeNames);
6 changes: 6 additions & 0 deletions backend/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ async function createSharedAppObject() {
minimizeMainWindow,
unminimizeMainWindow,
shell,
restartApp,
setDarkMode,
getConfigDir,
getFilesDir,
Expand Down Expand Up @@ -190,6 +191,11 @@ function isOSNotificationSupported(): boolean {
return Notification.isSupported();
}

function restartApp() {
app.relaunch();
app.quit();
}

function setDarkMode(mode: "system" | "light" | "dark") {
if (!["system", "light", "dark"].includes(mode)) {
throw new Error("Bad dark mode " + mode);
Expand Down

0 comments on commit 597bf47

Please sign in to comment.