feat: completely localize command palette actions, categories, and hooks
All checks were successful
Deploy Portfolio to VPS / deploy (push) Successful in 28s
All checks were successful
Deploy Portfolio to VPS / deploy (push) Successful in 28s
This commit is contained in:
parent
ad4d4b4387
commit
d52f1c088b
4 changed files with 60 additions and 22 deletions
|
|
@ -1,20 +1,23 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { getCommands } from "../lib/paletteCommands";
|
import { getCommands } from "../lib/paletteCommands";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interactive Cmd+K modal for quick navigation, actions, and social links
|
* Interactive Cmd+K modal for quick navigation, actions, and social links
|
||||||
*/
|
*/
|
||||||
export default function CommandPalette() {
|
export default function CommandPalette() {
|
||||||
|
const tCommands = useTranslations("Commands");
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Load action definitions from lib helper
|
// Load action definitions from lib helper safely inside the component
|
||||||
const commands = getCommands(copied, setCopied);
|
const commands = getCommands(copied, setCopied, tCommands);
|
||||||
|
|
||||||
// Filter commands dynamically by search query or category
|
// Filter commands dynamically by search query or category
|
||||||
const filteredCommands = commands.filter(
|
const filteredCommands = commands.filter(
|
||||||
|
|
@ -104,7 +107,7 @@ export default function CommandPalette() {
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type a command or action..."
|
placeholder={tCommands("searchPlaceholder")}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
|
|
@ -122,7 +125,7 @@ export default function CommandPalette() {
|
||||||
<div className="max-h-100 overflow-y-auto p-2 space-y-1">
|
<div className="max-h-100 overflow-y-auto p-2 space-y-1">
|
||||||
{filteredCommands.length === 0 ? (
|
{filteredCommands.length === 0 ? (
|
||||||
<p className="p-4 text-center text-xs font-mono text-slate-500">
|
<p className="p-4 text-center text-xs font-mono text-slate-500">
|
||||||
No matching commands found.
|
{tCommands("noResults")}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
filteredCommands.map((cmd, index) => {
|
filteredCommands.map((cmd, index) => {
|
||||||
|
|
@ -167,14 +170,14 @@ export default function CommandPalette() {
|
||||||
{/* Footer Hint */}
|
{/* Footer Hint */}
|
||||||
<div className="px-4 py-2.5 border-t border-slate-800/60 bg-slate-950/60 flex items-center justify-between text-[11px] font-mono text-slate-500">
|
<div className="px-4 py-2.5 border-t border-slate-800/60 bg-slate-950/60 flex items-center justify-between text-[11px] font-mono text-slate-500">
|
||||||
<span>
|
<span>
|
||||||
Use{" "}
|
{tCommands("navHint1")}{" "}
|
||||||
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
||||||
↑
|
↑
|
||||||
</kbd>{" "}
|
</kbd>{" "}
|
||||||
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
||||||
↓
|
↓
|
||||||
</kbd>{" "}
|
</kbd>{" "}
|
||||||
to navigate
|
{tCommands("navHint2")}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
<kbd className="text-slate-400 bg-slate-900 px-1 rounded border border-slate-800">
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,23 @@
|
||||||
"timeMinutesAgo": "vor {minutes}m",
|
"timeMinutesAgo": "vor {minutes}m",
|
||||||
"timeHoursAgo": "vor {hours}h"
|
"timeHoursAgo": "vor {hours}h"
|
||||||
},
|
},
|
||||||
|
"Commands": {
|
||||||
|
"catQuickActions": "Schnellzugriff",
|
||||||
|
"catSocial": "Soziale Medien / Links",
|
||||||
|
"catSystem": "System & Info",
|
||||||
|
"copyEmail": "E-Mail kopieren (dev@andre-kempf.com)",
|
||||||
|
"emailCopied": "E-Mail kopiert!",
|
||||||
|
"downloadCV": "Lebenslauf herunterladen (PDF)",
|
||||||
|
"openTerminal": "Statistik-Terminal öffnen",
|
||||||
|
"viewForgejo": "Git-Profil (Self-Hosted) ansehen",
|
||||||
|
"viewGitHub": "GitHub-Profil (Legacy) ansehen",
|
||||||
|
"viewLinkedIn": "LinkedIn-Profil ansehen",
|
||||||
|
"viewSource": "Quellcode ansehen (Self-Hosted Git)",
|
||||||
|
"searchPlaceholder": "Befehl oder Aktion eingeben...",
|
||||||
|
"noResults": "Keine passenden Befehle gefunden.",
|
||||||
|
"navHint1": "Mit",
|
||||||
|
"navHint2": "navigieren"
|
||||||
|
},
|
||||||
"Imprint": {
|
"Imprint": {
|
||||||
"backHome": "Zurück zur Startseite",
|
"backHome": "Zurück zur Startseite",
|
||||||
"section1Title": "Angaben gemäß § 5 DDG",
|
"section1Title": "Angaben gemäß § 5 DDG",
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,23 @@
|
||||||
"timeMinutesAgo": "{minutes}m ago",
|
"timeMinutesAgo": "{minutes}m ago",
|
||||||
"timeHoursAgo": "{hours}h ago"
|
"timeHoursAgo": "{hours}h ago"
|
||||||
},
|
},
|
||||||
|
"Commands": {
|
||||||
|
"catQuickActions": "Quick Actions",
|
||||||
|
"catSocial": "Social / Links",
|
||||||
|
"catSystem": "System & Info",
|
||||||
|
"copyEmail": "Copy Email (dev@andre-kempf.com)",
|
||||||
|
"emailCopied": "Email Copied!",
|
||||||
|
"downloadCV": "Download CV (PDF)",
|
||||||
|
"openTerminal": "Open Analytics Terminal",
|
||||||
|
"viewForgejo": "View Self-Hosted Git Profile",
|
||||||
|
"viewGitHub": "View GitHub (Legacy) Profile",
|
||||||
|
"viewLinkedIn": "View LinkedIn Profile",
|
||||||
|
"viewSource": "View Site Source Code (Self-Hosted Git)",
|
||||||
|
"searchPlaceholder": "Type a command or action...",
|
||||||
|
"noResults": "No matching commands found.",
|
||||||
|
"navHint1": "Use",
|
||||||
|
"navHint2": "to navigate"
|
||||||
|
},
|
||||||
"Imprint": {
|
"Imprint": {
|
||||||
"section1Title": "Information Pursuant to § 5 DDG",
|
"section1Title": "Information Pursuant to § 5 DDG",
|
||||||
"section1Sub": "Also responsible for content pursuant to § 18 Abs. 2 MStV.",
|
"section1Sub": "Also responsible for content pursuant to § 18 Abs. 2 MStV.",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { playSudoEasterEgg } from "./playSudoEasterEgg";
|
||||||
export type CommandItem = {
|
export type CommandItem = {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
category: "Quick Actions" | "System & Info" | "Social / Links";
|
category: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
};
|
};
|
||||||
|
|
@ -17,11 +17,12 @@ export type CommandItem = {
|
||||||
export const getCommands = (
|
export const getCommands = (
|
||||||
copied: boolean,
|
copied: boolean,
|
||||||
setCopied: (value: boolean) => void,
|
setCopied: (value: boolean) => void,
|
||||||
|
tCommands: (key: string) => string,
|
||||||
): CommandItem[] => [
|
): CommandItem[] => [
|
||||||
{
|
{
|
||||||
id: "copy-email",
|
id: "copy-email",
|
||||||
label: copied ? "Email Copied!" : "Copy Email (dev@andre-kempf.com)",
|
label: copied ? tCommands("emailCopied") : tCommands("copyEmail"),
|
||||||
category: "Quick Actions",
|
category: tCommands("catQuickActions"),
|
||||||
icon: copied ? "✓" : "✉️",
|
icon: copied ? "✓" : "✉️",
|
||||||
action: () => {
|
action: () => {
|
||||||
navigator.clipboard.writeText("dev@andre-kempf.com");
|
navigator.clipboard.writeText("dev@andre-kempf.com");
|
||||||
|
|
@ -31,8 +32,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "download-cv",
|
id: "download-cv",
|
||||||
label: "Download CV (PDF)",
|
label: tCommands("downloadCV"),
|
||||||
category: "Quick Actions",
|
category: tCommands("catQuickActions"),
|
||||||
icon: "📄",
|
icon: "📄",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
|
|
@ -44,8 +45,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "terminal",
|
id: "terminal",
|
||||||
label: "Open Analytics Terminal",
|
label: tCommands("openTerminal"),
|
||||||
category: "Quick Actions",
|
category: tCommands("catQuickActions"),
|
||||||
icon: "⚡",
|
icon: "⚡",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.dispatchEvent(new CustomEvent("open-analytics"));
|
window.dispatchEvent(new CustomEvent("open-analytics"));
|
||||||
|
|
@ -53,8 +54,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "forgejo",
|
id: "forgejo",
|
||||||
label: "View Self-Hosted Git Profile",
|
label: tCommands("viewForgejo"),
|
||||||
category: "Social / Links",
|
category: tCommands("catSocial"),
|
||||||
icon: "🔨",
|
icon: "🔨",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
|
|
@ -66,8 +67,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "github",
|
id: "github",
|
||||||
label: "View GitHub (Legacy) Profile",
|
label: tCommands("viewGitHub"),
|
||||||
category: "Social / Links",
|
category: tCommands("catSocial"),
|
||||||
icon: "🐙",
|
icon: "🐙",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
|
|
@ -79,8 +80,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "linkedin",
|
id: "linkedin",
|
||||||
label: "View LinkedIn Profile",
|
label: tCommands("viewLinkedIn"),
|
||||||
category: "Social / Links",
|
category: tCommands("catSocial"),
|
||||||
icon: "💼",
|
icon: "💼",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
|
|
@ -92,8 +93,8 @@ export const getCommands = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "source-code",
|
id: "source-code",
|
||||||
label: "View Site Source Code (Self-Hosted Git)",
|
label: tCommands("viewSource"),
|
||||||
category: "Social / Links",
|
category: tCommands("catSocial"),
|
||||||
icon: "💻",
|
icon: "💻",
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
|
|
@ -106,7 +107,7 @@ export const getCommands = (
|
||||||
{
|
{
|
||||||
id: "sudo-egg",
|
id: "sudo-egg",
|
||||||
label: "sudo rm -rf --no-preserve-root /",
|
label: "sudo rm -rf --no-preserve-root /",
|
||||||
category: "System & Info",
|
category: tCommands("catSystem"),
|
||||||
icon: "💀",
|
icon: "💀",
|
||||||
action: playSudoEasterEgg,
|
action: playSudoEasterEgg,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue