aboutsummaryrefslogtreecommitdiff
path: root/src/lib/CommandPalette
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-05-24 13:22:34 +0000
committerFuwn <[email protected]>2026-05-24 13:22:34 +0000
commit56a7a7851b09cb30a5cd543c8cb4f926109b4290 (patch)
treea620f908405fa48fd601580c5a48432831ec5c33 /src/lib/CommandPalette
parentfix(layout): preserve list panel when clicking action buttons in summary (diff)
downloaddue.moe-56a7a7851b09cb30a5cd543c8cb4f926109b4290.tar.xz
due.moe-56a7a7851b09cb30a5cd543c8cb4f926109b4290.zip
refactor(locale): move hardcoded UI strings into english locale
Adds optional namespaces (common, errors, commandPalette, headTitle, notifications, schedule, events, home, reader, routes, badgePreview, badgeWall) and extends existing ones (settings.*, lists.*, tools.*, user.*, hololive.*) on the Locale interface. New fields are optional so japanese.ts can omit them; svelte-i18n's fallbackLocale handles the runtime miss. HeadTitle gains an optional routeKey prop for type-safe lookup. defaultActions becomes a factory so the command palette re-reads locale on language toggle. The existing JP feedback translation in routes/settings is preserved via japanese.ts. Out of scope (kept hardcoded): service-worker.ts, app.html, Landing*.svelte, tools.ts registry, Easter Event 2025 pages.
Diffstat (limited to 'src/lib/CommandPalette')
-rw-r--r--src/lib/CommandPalette/CommandPalette.svelte16
-rw-r--r--src/lib/CommandPalette/actions.ts356
-rw-r--r--src/lib/CommandPalette/authActions.ts8
-rw-r--r--src/lib/CommandPalette/syncActions.ts35
-rw-r--r--src/lib/CommandPalette/toggleActions.ts56
5 files changed, 256 insertions, 215 deletions
diff --git a/src/lib/CommandPalette/CommandPalette.svelte b/src/lib/CommandPalette/CommandPalette.svelte
index b68d468d..04eabff9 100644
--- a/src/lib/CommandPalette/CommandPalette.svelte
+++ b/src/lib/CommandPalette/CommandPalette.svelte
@@ -3,6 +3,7 @@ import { onMount } from "svelte";
import { fly, fade } from "svelte/transition";
import { flip } from "svelte/animate";
import type { CommandPaletteAction } from "./actions";
+import locale from "$stores/locale";
export let items: CommandPaletteAction[] = [];
export let open = false;
@@ -176,7 +177,7 @@ const handleGlobalKey = (e: KeyboardEvent) => {
class="dropdown {open ? 'fade-in' : 'fade-out'}"
role="dialog"
aria-modal="true"
- aria-label="Command palette"
+ aria-label={$locale().commandPalette?.ariaLabel}
>
<div class="dropdown-content card card-small card-glass">
<input
@@ -184,8 +185,8 @@ const handleGlobalKey = (e: KeyboardEvent) => {
bind:value={search}
class="command-input"
type="text"
- placeholder="Search"
- aria-label="Search commands"
+ placeholder={$locale().commandPalette?.placeholder}
+ aria-label={$locale().commandPalette?.searchCommands}
autocomplete="off"
spellcheck="false"
role="combobox"
@@ -196,7 +197,12 @@ const handleGlobalKey = (e: KeyboardEvent) => {
onkeydown={handleKey}
/>
- <div class="results-container" role="listbox" id={listboxId} aria-label="Commands">
+ <div
+ class="results-container"
+ role="listbox"
+ id={listboxId}
+ aria-label={$locale().commandPalette?.commands}
+ >
{#each filtered as item, i (item.id || item.url)}
<a
id={optionDomId(i)}
@@ -220,7 +226,7 @@ const handleGlobalKey = (e: KeyboardEvent) => {
{#if filtered.length === 0 && search !== ''}
<div class="no-results opaque" role="status" in:fade={{ duration: 150 }}>
- No results found
+ {$locale().commandPalette?.noResults}
</div>
{/if}
</div>
diff --git a/src/lib/CommandPalette/actions.ts b/src/lib/CommandPalette/actions.ts
index 9915fe66..d2ed5a1d 100644
--- a/src/lib/CommandPalette/actions.ts
+++ b/src/lib/CommandPalette/actions.ts
@@ -1,4 +1,6 @@
import { invalidateListCaches } from "$lib/Media/invalidate";
+import locale from "$stores/locale";
+import { get } from "svelte/store";
export interface CommandPaletteAction {
name: string;
@@ -9,178 +11,182 @@ export interface CommandPaletteAction {
actions?: CommandPaletteAction[];
}
-export const defaultActions: CommandPaletteAction[] = [
- {
- name: "Home",
- url: "/",
- tags: [
- "main",
- "manga",
- "anime",
- "light",
- "dashboard",
- "start",
- "begin",
- "novels",
- "list",
- ],
- actions: [
- {
- name: "Upcoming Episodes",
- url: "/",
- tags: ["anime", "list"],
- },
- {
- name: "Not Yet Released",
- url: "/",
- tags: ["anime", "schedule", "list"],
- },
- {
- name: "Due Episodes",
- url: "/",
- tags: ["anime", "list"],
- },
- {
- name: "Manga & Light Novels",
- url: "/",
- tags: ["novels", "manga", "list"],
- },
- ],
- },
- {
- name: "Completed",
- url: "/completed",
- tags: [
- "finish",
- "end",
- "done",
- "finish",
- "end",
- "done",
- "anime",
- "novels",
- "manga",
- ],
- actions: [
- {
- name: "Anime",
- url: "/completed",
- tags: ["anime", "list"],
- },
- {
- name: "Manga & Light Novels",
- url: "/completed",
- tags: ["novels", "manga", "list"],
- },
- ],
- },
- {
- name: "Subtitle Schedule",
- url: "/schedule",
- tags: ["anime", "subs"],
- },
- {
- name: "hololive Schedule",
- url: "/hololive",
- tags: ["vtuber", "youtube", "virtual", "twitch", "stream"],
- },
- {
- name: "Character Birthdays",
- url: "/birthdays",
- tags: ["schedule", "vtuber", "date"],
- },
- {
- name: "New Releases",
- url: "/releases",
- tags: ["novels", "manga", "date", "schedule", "time"],
- },
- {
- name: "Settings",
- url: "/settings",
- tags: [
- "sync",
- "display",
- "hide",
- "panels",
- "motion",
- "accessibility",
- "notifications",
- "rss",
- "warning",
- "show",
- "links",
- "sort",
- "calculation",
- "cache",
- "clear",
- "debug",
- "language",
- "locale",
- ],
- actions: [
- {
- name: "Settings Sync",
- url: "/settings#sync",
- tags: ["settings"],
- },
- {
- name: "RSS Feeds",
- url: "/settings#feeds",
- tags: ["settings"],
- },
- {
- name: "Display",
- url: "/settings",
- tags: ["settings"],
- },
- {
- name: "Calculation",
- url: "/settings",
- tags: ["settings"],
- },
- {
- name: "Cache",
- url: "/settings",
- tags: ["settings"],
- },
- {
- name: "Debug",
- url: "/settings#debug",
- tags: ["settings"],
- },
- ],
- },
- {
- name: "My Profile",
- url: "/user",
- tags: ["user", "me", "settings"],
- actions: [
- {
- name: "User Preferences",
- url: "/user",
- tags: ["user", "me", "settings"],
- },
- ],
- },
- {
- name: "My Badge Wall",
- url: "/user?badges=1",
- tags: ["user", "me", "settings"],
- },
- {
- name: "Refresh Anime & Manga List Caches",
- url: "",
- preventDefault: true,
- tags: [
- "cache",
- "clear",
- "refresh",
- "invalidate",
- "debug",
- "anime",
- "manga",
- "list",
- ],
- onClick: invalidateListCaches,
- },
-];
+export const defaultActions = (): CommandPaletteAction[] => {
+ const l = get(locale)();
+
+ return [
+ {
+ name: l.navigation.home,
+ url: "/",
+ tags: [
+ "main",
+ "manga",
+ "anime",
+ "light",
+ "dashboard",
+ "start",
+ "begin",
+ "novels",
+ "list",
+ ],
+ actions: [
+ {
+ name: l.lists.upcoming.episodes.title,
+ url: "/",
+ tags: ["anime", "list"],
+ },
+ {
+ name: l.lists.upcoming.notYetReleased.title,
+ url: "/",
+ tags: ["anime", "schedule", "list"],
+ },
+ {
+ name: l.lists.due.episodes.title,
+ url: "/",
+ tags: ["anime", "list"],
+ },
+ {
+ name: l.lists.due.mangaAndLightNovels.title,
+ url: "/",
+ tags: ["novels", "manga", "list"],
+ },
+ ],
+ },
+ {
+ name: l.navigation.completed,
+ url: "/completed",
+ tags: [
+ "finish",
+ "end",
+ "done",
+ "finish",
+ "end",
+ "done",
+ "anime",
+ "novels",
+ "manga",
+ ],
+ actions: [
+ {
+ name: l.settings.media.anime,
+ url: "/completed",
+ tags: ["anime", "list"],
+ },
+ {
+ name: l.lists.completed.mangaAndLightNovels.title,
+ url: "/completed",
+ tags: ["novels", "manga", "list"],
+ },
+ ],
+ },
+ {
+ name: l.navigation.subtitleSchedule,
+ url: "/schedule",
+ tags: ["anime", "subs"],
+ },
+ {
+ name: l.navigation.hololive,
+ url: "/hololive",
+ tags: ["vtuber", "youtube", "virtual", "twitch", "stream"],
+ },
+ {
+ name: l.tools.tool.characterBirthdays.short,
+ url: "/birthdays",
+ tags: ["schedule", "vtuber", "date"],
+ },
+ {
+ name: l.navigation.newReleases,
+ url: "/releases",
+ tags: ["novels", "manga", "date", "schedule", "time"],
+ },
+ {
+ name: l.navigation.settings,
+ url: "/settings",
+ tags: [
+ "sync",
+ "display",
+ "hide",
+ "panels",
+ "motion",
+ "accessibility",
+ "notifications",
+ "rss",
+ "warning",
+ "show",
+ "links",
+ "sort",
+ "calculation",
+ "cache",
+ "clear",
+ "debug",
+ "language",
+ "locale",
+ ],
+ actions: [
+ {
+ name: l.settings.settingsSync.title,
+ url: "/settings#sync",
+ tags: ["settings"],
+ },
+ {
+ name: l.settings.rssFeeds.title,
+ url: "/settings#feeds",
+ tags: ["settings"],
+ },
+ {
+ name: l.settings.display.title,
+ url: "/settings",
+ tags: ["settings"],
+ },
+ {
+ name: l.settings.calculation.title,
+ url: "/settings",
+ tags: ["settings"],
+ },
+ {
+ name: l.settings.cache.title,
+ url: "/settings",
+ tags: ["settings"],
+ },
+ {
+ name: l.settings.debug.title,
+ url: "/settings#debug",
+ tags: ["settings"],
+ },
+ ],
+ },
+ {
+ name: l.navigation.myProfile,
+ url: "/user",
+ tags: ["user", "me", "settings"],
+ actions: [
+ {
+ name: l.user.preferences.title,
+ url: "/user",
+ tags: ["user", "me", "settings"],
+ },
+ ],
+ },
+ {
+ name: l.navigation.myBadgeWall,
+ url: "/user?badges=1",
+ tags: ["user", "me", "settings"],
+ },
+ {
+ name: l.commandPalette?.refreshCaches ?? "Refresh Anime & Manga List Caches",
+ url: "",
+ preventDefault: true,
+ tags: [
+ "cache",
+ "clear",
+ "refresh",
+ "invalidate",
+ "debug",
+ "anime",
+ "manga",
+ "list",
+ ],
+ onClick: invalidateListCaches,
+ },
+ ];
+};
diff --git a/src/lib/CommandPalette/authActions.ts b/src/lib/CommandPalette/authActions.ts
index c306eb34..9dbc9565 100644
--- a/src/lib/CommandPalette/authActions.ts
+++ b/src/lib/CommandPalette/authActions.ts
@@ -1,15 +1,19 @@
import { env } from "$env/dynamic/public";
import root from "$lib/Utility/root";
import localforage from "localforage";
+import locale from "$stores/locale";
+import { get } from "svelte/store";
import type { CommandPaletteAction } from "./actions";
export const authActions = (
user: string | undefined,
): CommandPaletteAction[] => {
+ const l = get(locale)();
+
if (user)
return [
{
- name: "Log Out",
+ name: l.commandPalette?.logOut ?? "Log Out",
url: "#",
preventDefault: true,
tags: ["auth", "sign", "out", "user"],
@@ -29,7 +33,7 @@ export const authActions = (
return [
{
- name: "Log In",
+ name: l.commandPalette?.logIn ?? "Log In",
url: loginUrl,
preventDefault: true,
tags: ["auth", "sign", "in", "anilist"],
diff --git a/src/lib/CommandPalette/syncActions.ts b/src/lib/CommandPalette/syncActions.ts
index 90c6a931..b3cf01c6 100644
--- a/src/lib/CommandPalette/syncActions.ts
+++ b/src/lib/CommandPalette/syncActions.ts
@@ -4,6 +4,7 @@ import root from "$lib/Utility/root";
import settings from "$stores/settings";
import settingsSyncPulled from "$stores/settingsSyncPulled";
import settingsSyncTimes from "$stores/settingsSyncTimes";
+import locale from "$stores/locale";
import { get } from "svelte/store";
import type { CommandPaletteAction } from "./actions";
@@ -13,9 +14,11 @@ export const syncActions = (
): CommandPaletteAction[] => {
if (identityId <= 0) return [];
+ const l = get(locale)();
+
const actions: CommandPaletteAction[] = [
{
- name: "Push Settings Now",
+ name: l.commandPalette?.sync?.pushNow ?? "Push Settings Now",
url: "#",
preventDefault: true,
tags: ["settings", "sync", "push", "upload", "remote"],
@@ -32,8 +35,10 @@ export const syncActions = (
addNotification(
options({
- heading: "Settings Sync",
- description: "Pushed local configuration to remote",
+ heading: get(locale)().settings.settingsSync.title,
+ description:
+ get(locale)().commandPalette?.sync?.pushedDescription ??
+ "Pushed local configuration to remote",
}),
);
@@ -46,7 +51,7 @@ export const syncActions = (
},
},
{
- name: "Pull Settings Now",
+ name: l.commandPalette?.sync?.pullNow ?? "Pull Settings Now",
url: "#",
preventDefault: true,
tags: ["settings", "sync", "pull", "download", "remote"],
@@ -61,8 +66,10 @@ export const syncActions = (
if (!data?.configuration) {
addNotification(
options({
- heading: "Settings Sync",
- description: "No remote configuration found",
+ heading: get(locale)().settings.settingsSync.title,
+ description:
+ get(locale)().commandPalette?.sync?.noRemoteFound ??
+ "No remote configuration found",
}),
);
@@ -77,7 +84,11 @@ export const syncActions = (
});
addNotification(
- options({ heading: "Pulled remote configuration" }),
+ options({
+ heading:
+ get(locale)().notifications?.pulledRemote ??
+ "Pulled remote configuration",
+ }),
);
});
})
@@ -88,14 +99,20 @@ export const syncActions = (
if (syncEnabled)
actions.push({
- name: "Disable Settings Sync",
+ name: l.commandPalette?.sync?.disable ?? "Disable Settings Sync",
url: "#",
preventDefault: true,
tags: ["settings", "sync", "disable", "off", "stop"],
onClick: () => {
settings.setKey("settingsSync", false);
- addNotification(options({ heading: "Settings sync disabled" }));
+ addNotification(
+ options({
+ heading:
+ get(locale)().notifications?.syncDisabled ??
+ "Settings sync disabled",
+ }),
+ );
},
});
diff --git a/src/lib/CommandPalette/toggleActions.ts b/src/lib/CommandPalette/toggleActions.ts
index ca64ff5f..9ba1d121 100644
--- a/src/lib/CommandPalette/toggleActions.ts
+++ b/src/lib/CommandPalette/toggleActions.ts
@@ -1,4 +1,6 @@
import settings, { type Settings } from "$stores/settings";
+import locale from "$stores/locale";
+import { get } from "svelte/store";
import type { CommandPaletteAction } from "./actions";
const TITLE_FORMATS: Settings["displayTitleFormat"][] = [
@@ -51,82 +53,84 @@ export const toggleActions = (current: Settings): CommandPaletteAction[] => {
const nextOutbound =
OUTBOUND_TARGETS[(outboundIndex + 1) % OUTBOUND_TARGETS.length];
+ const t = get(locale)().commandPalette?.toggles;
+
return [
boolToggle(
current.display24HourTime,
"display24HourTime",
- "Switch to 24-hour time",
- "Switch to 12-hour time",
+ t?.time24On ?? "Switch to 24-hour time",
+ t?.time24Off ?? "Switch to 12-hour time",
["time", "clock", "24h", "12h", "format"],
),
boolToggle(
current.displayDisableAnimations,
"displayDisableAnimations",
- "Disable animations",
- "Enable animations",
+ t?.animationsOff ?? "Disable animations",
+ t?.animationsOn ?? "Enable animations",
["motion", "animation", "accessibility"],
),
boolToggle(
current.displayBlurAdultContent,
"displayBlurAdultContent",
- "Blur adult content",
- "Show adult content unblurred",
+ t?.blurAdultOn ?? "Blur adult content",
+ t?.blurAdultOff ?? "Show adult content unblurred",
["nsfw", "adult", "blur", "censor"],
),
boolToggle(
current.displayCoverModeAnime,
"displayCoverModeAnime",
- "Show anime covers",
- "Hide anime covers",
+ t?.showAnimeCovers ?? "Show anime covers",
+ t?.hideAnimeCovers ?? "Hide anime covers",
["cover", "image", "anime", "display"],
),
boolToggle(
current.displayCoverModeManga,
"displayCoverModeManga",
- "Show manga covers",
- "Hide manga covers",
+ t?.showMangaCovers ?? "Show manga covers",
+ t?.hideMangaCovers ?? "Hide manga covers",
["cover", "image", "manga", "novels", "display"],
),
boolToggle(
current.displayHoverCover,
"displayHoverCover",
- "Enable hover cover preview",
- "Disable hover cover preview",
+ t?.hoverCoverOn ?? "Enable hover cover preview",
+ t?.hoverCoverOff ?? "Disable hover cover preview",
["cover", "hover", "preview"],
),
boolToggle(
current.displayScheduleListMode,
"displayScheduleListMode",
- "Schedule: list mode",
- "Schedule: grid mode",
+ t?.scheduleListMode ?? "Schedule: list mode",
+ t?.scheduleGridMode ?? "Schedule: grid mode",
["schedule", "list", "grid", "view"],
),
boolToggle(
current.displayReverseSort,
"displayReverseSort",
- "Reverse sort order",
- "Restore default sort order",
+ t?.reverseSort ?? "Reverse sort order",
+ t?.restoreSort ?? "Restore default sort order",
["sort", "reverse", "order"],
),
boolToggle(
current.displayDataSaver,
"displayDataSaver",
- "Enable data saver",
- "Disable data saver",
+ t?.dataSaverOn ?? "Enable data saver",
+ t?.dataSaverOff ?? "Disable data saver",
["data", "bandwidth", "saver", "performance"],
),
boolToggle(
current.displayDisableNotifications,
"displayDisableNotifications",
- "Disable in-app notifications",
- "Enable in-app notifications",
+ t?.notificationsOff ?? "Disable in-app notifications",
+ t?.notificationsOn ?? "Enable in-app notifications",
["notifications", "alerts", "toast"],
),
{
name:
current.displayLanguage === "en"
- ? "Switch language to 日本語"
- : "Switch language to English",
+ ? (t?.switchLanguageJa ?? "Switch language to 日本語")
+ : (t?.switchLanguageEn ?? "Switch language to English"),
url: "#",
preventDefault: true,
tags: ["language", "locale", "translate", "japanese", "english"],
@@ -137,14 +141,18 @@ export const toggleActions = (current: Settings): CommandPaletteAction[] => {
),
},
{
- name: `Title format: ${current.displayTitleFormat} → ${nextTitle}`,
+ name: (t?.titleFormat ?? "Title format: {from} → {to}")
+ .replace("{from}", current.displayTitleFormat)
+ .replace("{to}", nextTitle),
url: "#",
preventDefault: true,
tags: ["title", "format", "english", "romaji", "native"],
onClick: () => settings.setKey("displayTitleFormat", nextTitle),
},
{
- name: `Outbound links: ${OUTBOUND_LABELS[current.displayOutboundLinksTo]} → ${OUTBOUND_LABELS[nextOutbound]}`,
+ name: (t?.outboundLinks ?? "Outbound links: {from} → {to}")
+ .replace("{from}", OUTBOUND_LABELS[current.displayOutboundLinksTo])
+ .replace("{to}", OUTBOUND_LABELS[nextOutbound]),
url: "#",
preventDefault: true,
tags: [