web: i18n system & navbar translations
dynamic page language and language dropdown!! finally!!
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1, maximum-scale=1" />
|
||||
|
||||
28
web/src/components/settings/LanguageDropdown.svelte
Normal file
28
web/src/components/settings/LanguageDropdown.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import settings, { updateSetting } from "$lib/settings";
|
||||
import { t, locale, locales } from "$lib/i18n/translations";
|
||||
|
||||
import languages from "$i18n/languages.json";
|
||||
|
||||
$: currentSetting = $settings.appearance.language;
|
||||
|
||||
const updateLocale = (lang: string) => {
|
||||
updateSetting({
|
||||
appearance: {
|
||||
language: lang as keyof typeof languages,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<select
|
||||
id="setting-dropdown-appearance-language"
|
||||
bind:value={$locale}
|
||||
on:change={() => updateLocale($locale)}
|
||||
>
|
||||
{#each $locales as value}
|
||||
<option value={value} selected={currentSetting === value}>
|
||||
{$t(`languages.${value}`)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { t } from "$lib/i18n/translations";
|
||||
|
||||
import CobaltLogo from "$components/sidebar/CobaltLogo.svelte";
|
||||
import SidebarTab from "$components/sidebar/SidebarTab.svelte";
|
||||
|
||||
@@ -23,7 +25,7 @@
|
||||
|
||||
<svelte:window bind:innerWidth={screenWidth} />
|
||||
|
||||
<nav id="sidebar">
|
||||
<nav id="sidebar" aria-label={$t("a11y.tabs.tabPanel")}>
|
||||
<CobaltLogo />
|
||||
<div id="sidebar-tabs">
|
||||
<div id="sidebar-actions" class="sidebar-inner-container">
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
|
||||
import { t } from "$lib/i18n/translations";
|
||||
|
||||
export let tabName: string;
|
||||
export let tabLink: string;
|
||||
|
||||
const firstTabPage = [
|
||||
"save",
|
||||
"settings",
|
||||
"updates"
|
||||
];
|
||||
const firstTabPage = ["save", "settings", "updates"];
|
||||
|
||||
let tab: HTMLElement;
|
||||
|
||||
$: currentTab = $page.url.pathname.split('/')[1];
|
||||
$: baseTabPath = tabLink.split('/')[1]
|
||||
$: currentTab = $page.url.pathname.split("/")[1];
|
||||
$: baseTabPath = tabLink.split("/")[1];
|
||||
|
||||
$: isTabActive = currentTab === baseTabPath;
|
||||
|
||||
const showTab = (e: HTMLElement | undefined) => {
|
||||
if (e) {
|
||||
e.scrollIntoView({
|
||||
inline: firstTabPage.includes(tabName) ? 'end' : 'start',
|
||||
behavior: 'smooth'
|
||||
inline: firstTabPage.includes(tabName) ? "end" : "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$: if (isTabActive) {
|
||||
showTab(tab)
|
||||
showTab(tab);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -38,9 +36,10 @@
|
||||
href={tabLink}
|
||||
bind:this={tab}
|
||||
on:focus={() => showTab(tab)}
|
||||
role="tab"
|
||||
>
|
||||
<slot></slot>
|
||||
{tabName}
|
||||
{$t(`tabs.${tabName}`)}
|
||||
</a>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -4,10 +4,14 @@ const isIOS = ua.includes("iphone os") || (ua.includes("mac os") && navigator.ma
|
||||
const isAndroid = ua.includes("android") || ua.includes("diordna");
|
||||
const isMobile = isIOS || isAndroid;
|
||||
|
||||
const deviceInfo = {
|
||||
const preferredLocale = navigator.language.toLowerCase().slice(0, 2);
|
||||
|
||||
const device = {
|
||||
isIOS,
|
||||
isAndroid,
|
||||
isMobile,
|
||||
|
||||
preferredLocale,
|
||||
}
|
||||
|
||||
export default deviceInfo;
|
||||
export default device;
|
||||
|
||||
48
web/src/lib/i18n/translations.ts
Normal file
48
web/src/lib/i18n/translations.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import i18n from 'sveltekit-i18n';
|
||||
import type { Config } from 'sveltekit-i18n';
|
||||
|
||||
import languages from '$i18n/languages.json';
|
||||
|
||||
export const defaultLocale = 'en';
|
||||
|
||||
export const config: Config = {
|
||||
translations: {
|
||||
en: { languages },
|
||||
ru: { languages },
|
||||
},
|
||||
loaders: [
|
||||
{
|
||||
locale: 'en',
|
||||
key: 'tabs',
|
||||
loader: async () => (
|
||||
await import(`$i18n/en/tabs.json`)
|
||||
).default,
|
||||
},
|
||||
{
|
||||
locale: 'en',
|
||||
key: 'a11y.tabs',
|
||||
loader: async () => (
|
||||
await import(`$i18n/en/a11y/tabs.json`)
|
||||
).default,
|
||||
},
|
||||
{
|
||||
locale: 'ru',
|
||||
key: 'tabs',
|
||||
loader: async () => (
|
||||
await import(`$i18n/ru/tabs.json`)
|
||||
).default,
|
||||
},
|
||||
{
|
||||
locale: 'ru',
|
||||
key: 'a11y.tabs',
|
||||
loader: async () => (
|
||||
await import(`$i18n/ru/a11y/tabs.json`)
|
||||
).default,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const {
|
||||
t, loading, locales, locale, translations,
|
||||
loadTranslations, addTranslations, setLocale, setRoute
|
||||
} = new i18n(config);
|
||||
@@ -1,18 +1,21 @@
|
||||
import { defaultLocale } from "$lib/i18n/translations";
|
||||
import type { CobaltSettings } from "$lib/types/settings";
|
||||
|
||||
const defaultSettings: CobaltSettings = {
|
||||
schemaVersion: 1,
|
||||
accessibility: {
|
||||
reduceAnimations: false,
|
||||
reduceTransparency: false
|
||||
reduceTransparency: false,
|
||||
},
|
||||
appearance: {
|
||||
theme: "auto"
|
||||
theme: "auto",
|
||||
language: defaultLocale,
|
||||
autoLanguage: true,
|
||||
},
|
||||
general: {
|
||||
customProcessingEndpoint: "",
|
||||
seenOnboarding: false,
|
||||
seenSafetyWarning: false
|
||||
seenSafetyWarning: false,
|
||||
},
|
||||
save: {
|
||||
audioFormat: "mp3",
|
||||
@@ -25,22 +28,11 @@ const defaultSettings: CobaltSettings = {
|
||||
twitterGif: false,
|
||||
videoQuality: "720",
|
||||
youtubeVideoCodec: "h264",
|
||||
youtubeDubBrowserLang: false
|
||||
youtubeDubBrowserLang: false,
|
||||
},
|
||||
privacy: {
|
||||
trafficAnalytics: true
|
||||
}
|
||||
trafficAnalytics: true,
|
||||
},
|
||||
}
|
||||
export default defaultSettings;
|
||||
|
||||
export const settingArrays = {
|
||||
appearance: {
|
||||
theme: ["auto", "light", "dark"]
|
||||
},
|
||||
save: {
|
||||
audioFormat: ["best", "mp3", "ogg", "wav", "opus"],
|
||||
filenameStyle: ["classic", "basic", "pretty", "nerdy"],
|
||||
videoQuality: ["max", "2160", "1440", "1080", "720", "480", "360", "240", "144"],
|
||||
youtubeVideoCodec: ["h264", "av1", "vp9"],
|
||||
},
|
||||
}
|
||||
export default defaultSettings;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import languages from '$i18n/languages.json';
|
||||
|
||||
export type CobaltSettingsAccessibility = {
|
||||
reduceAnimations: boolean,
|
||||
reduceTransparency: boolean,
|
||||
@@ -12,6 +14,8 @@ export const youtubeVideoCodecOptions = ["h264", "av1", "vp9"] as const;
|
||||
|
||||
type CobaltSettingsAppearance = {
|
||||
theme: typeof themeOptions[number],
|
||||
language: keyof typeof languages,
|
||||
autoLanguage: boolean,
|
||||
};
|
||||
|
||||
type CobaltSettingsGeneral = {
|
||||
|
||||
@@ -1,2 +1,39 @@
|
||||
export const prerender = true;
|
||||
export const ssr = false;
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { get } from 'svelte/store';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
|
||||
import languages from '$i18n/languages.json';
|
||||
import { loadTranslations, defaultLocale } from '$lib/i18n/translations';
|
||||
|
||||
import device from '$lib/device.js';
|
||||
|
||||
export const load: Load = async ({ url }) => {
|
||||
const { pathname } = url;
|
||||
|
||||
let preferredLocale = defaultLocale;
|
||||
|
||||
if (browser) {
|
||||
const settings = get((await import('$lib/settings')).default);
|
||||
const deviceLanguage = device.preferredLocale;
|
||||
const settingsLanguage = settings.appearance.language;
|
||||
|
||||
const isValid = (lang: string) => (
|
||||
Object.keys(languages).includes(lang)
|
||||
);
|
||||
|
||||
if (settings.appearance.autoLanguage) {
|
||||
if (isValid(deviceLanguage)) {
|
||||
preferredLocale = deviceLanguage;
|
||||
}
|
||||
} else if (isValid(settingsLanguage)) {
|
||||
preferredLocale = settingsLanguage
|
||||
}
|
||||
}
|
||||
|
||||
await loadTranslations(preferredLocale, pathname);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import SettingsToggle from "$components/buttons/SettingsToggle.svelte";
|
||||
|
||||
import { themeOptions } from "$lib/types/settings";
|
||||
import LanguageDropdown from "$components/settings/LanguageDropdown.svelte";
|
||||
</script>
|
||||
|
||||
<SettingsCategory title="theme">
|
||||
@@ -31,3 +32,13 @@
|
||||
description="replaces rapid animations with smooth transitions."
|
||||
/>
|
||||
</SettingsCategory>
|
||||
|
||||
<SettingsCategory title="language">
|
||||
<LanguageDropdown />
|
||||
<SettingsToggle
|
||||
settingContext="appearance"
|
||||
settingId="autoLanguage"
|
||||
title="use default browser language"
|
||||
description="automatically picks the best language for you. if preferred browser language isn't available, english is used instead."
|
||||
/>
|
||||
</SettingsCategory>
|
||||
|
||||
Reference in New Issue
Block a user