diff --git a/src/app/admin/import/clothing/import-clothing-client.tsx b/src/app/admin/import/clothing/import-clothing-client.tsx index 07140aa0..47277540 100644 --- a/src/app/admin/import/clothing/import-clothing-client.tsx +++ b/src/app/admin/import/clothing/import-clothing-client.tsx @@ -10,6 +10,7 @@ import { Trash2, X, } from "lucide-react"; +// import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { diff --git a/src/app/admin/import/furni/nitro-editor-dialog.tsx b/src/app/admin/import/furni/nitro-editor-dialog.tsx index 610a3088..0fcaafc9 100644 --- a/src/app/admin/import/furni/nitro-editor-dialog.tsx +++ b/src/app/admin/import/furni/nitro-editor-dialog.tsx @@ -147,6 +147,7 @@ export function NitroEditorDialog({ onOpenChange, }: NitroEditorDialogProps) { const loadingT = useTranslations("pages.admin.housekeeping"); + const t = useTranslations("pages.admin.import.furni.nitroEditorDialog"); const [metadata, setMetadata] = useState(null); const [originalMetadata, setOriginalMetadata] = useState(null); @@ -191,7 +192,7 @@ export function NitroEditorDialog({ cansiton: result.canSit, canlayon: result.canLay, }); - toast.success(`Auto-detected: ${result.reason}`); + toast.success(t("autoDetected", { reason: result.reason })); }; const patchMeta = (path: string, value: unknown) => { @@ -217,7 +218,7 @@ export function NitroEditorDialog({ const data = await res.json(); if (controller.signal.aborted) return; if (!res.ok) { - toast.error(data.error || "Failed to load .nitro metadata"); + toast.error(data.error || t("loadFailed")); closeAfterLoadError(); return; } @@ -228,7 +229,7 @@ export function NitroEditorDialog({ setOriginalFlags({ ...data.flags }); } catch { if (!controller.signal.aborted) { - toast.error("Network error loading .nitro metadata"); + toast.error(t("networkErrorLoad")); closeAfterLoadError(); } } finally { @@ -245,7 +246,7 @@ export function NitroEditorDialog({ setMetadata(parsed); setJsonError(null); } catch { - toast.error("Invalid JSON — fix it before switching tabs"); + toast.error(t("invalidJsonSwitchTabs")); return; } } else if (activeTab !== "json" && newTab === "json") { @@ -260,7 +261,7 @@ export function NitroEditorDialog({ setFlags({ ...originalFlags }); setJsonText(JSON.stringify(originalMetadata, null, 2)); setJsonError(null); - toast.info("Reset to original values"); + toast.info(t("resetToOriginal")); }; const handleSave = async () => { @@ -270,7 +271,7 @@ export function NitroEditorDialog({ try { metadataToSave = JSON.parse(jsonText); } catch { - toast.error("Invalid JSON"); + toast.error(t("invalidJson")); return; } } @@ -289,7 +290,7 @@ export function NitroEditorDialog({ if (!res.ok) { const err = await res.json().catch(() => ({})); - toast.error(err.error || "Save failed"); + toast.error(err.error || t("saveFailed")); return; } diff --git a/src/app/admin/import/furni/upload/upload-furni-client.tsx b/src/app/admin/import/furni/upload/upload-furni-client.tsx index 1789a3f1..3ce3ec3e 100644 --- a/src/app/admin/import/furni/upload/upload-furni-client.tsx +++ b/src/app/admin/import/furni/upload/upload-furni-client.tsx @@ -1,5 +1,6 @@ "use client"; +import { useTranslations } from "next-intl"; import { AlertCircle, Check, Loader2, Upload } from "lucide-react"; import { useRef, useState } from "react"; import { toast } from "sonner"; @@ -22,6 +23,9 @@ interface UploadResult { } export function UploadFurniClient() { + const t = useTranslations( + "pages.admin.import.furni.upload.uploadFurniClient", + ); const nitroRef = useRef(null); const iconRef = useRef(null); @@ -73,20 +77,19 @@ export function UploadFurniClient() { const trimmedClass = classname.trim(); if (!CLASSNAME_RE.test(trimmedClass)) { - const msg = - "Classname must contain only letters, numbers, and underscores"; + const msg = t("errorClassname"); setError(msg); toast.error(msg); return; } if (!nitroFile) { - const msg = "Nitro file is required"; + const msg = t("errorNitroRequired"); setError(msg); toast.error(msg); return; } if (nitroFile.size > NITRO_MAX_BYTES) { - const msg = "Nitro file must be 50MB or smaller"; + const msg = t("errorNitroSize"); setError(msg); toast.error(msg); return; diff --git a/src/app/admin/moderation/cfh/[id]/cfh-detail.tsx b/src/app/admin/moderation/cfh/[id]/cfh-detail.tsx index 0aadf1b8..493875c8 100644 --- a/src/app/admin/moderation/cfh/[id]/cfh-detail.tsx +++ b/src/app/admin/moderation/cfh/[id]/cfh-detail.tsx @@ -11,6 +11,7 @@ import { VolumeX, Zap, } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { assignCfhTicket, @@ -36,16 +37,14 @@ import { Textarea } from "@/components/ui/textarea"; import { useServerAction } from "@/hooks/use-server-action"; import { formatDate } from "@/lib/format-date"; -const CFH_STATES: Record = { +const CFH_STATES: Record = { 0: { - label: "Open", class: "bg-[var(--admin-error-subtle)] text-[var(--admin-error)]", }, 1: { - label: "Picked", class: "bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)]", }, - 2: { label: "Closed", class: "bg-muted text-muted-foreground" }, + 2: { class: "bg-muted text-muted-foreground" }, }; interface UserInfo { @@ -97,11 +96,18 @@ export function CfhDetail({ userLinkMode?: "admin" | "public"; }) { void currentUserId; + const t = useTranslations("pages.admin.moderation.cfh.id.cfhDetail"); const { run, isPending } = useServerAction(); const [alertMsg, setAlertMsg] = useState(""); const [muteDuration, setMuteDuration] = useState(10); const state = CFH_STATES[ticket.state] ?? CFH_STATES[0]; + const stateKey = + ticket.state === 1 + ? "statePicked" + : ticket.state === 2 + ? "stateClosed" + : "stateOpen"; function userHref(user: UserInfo) { if (userLinkMode === "public") { @@ -122,17 +128,22 @@ export function CfhDetail({
- CFH #{ticket.id} + {t("cfhId", { id: ticket.id })} - {state.label} + {t(stateKey)} - Type {ticket.type} · Category {ticket.category} + {t("typeCategory", { + type: ticket.type, + category: ticket.category, + })}
-

Call For Help Report

+

{t("title")}

- {formatDate(new Date(ticket.timestamp * 1000))} · Room # - {ticket.roomId} + {t("roomMeta", { + when: formatDate(new Date(ticket.timestamp * 1000)), + room: ticket.roomId, + })}

@@ -145,12 +156,12 @@ export function CfhDetail({ - Report Message + {t("reportMessage")}

- {ticket.issue || "No description provided."} + {ticket.issue || t("noDescription")}

diff --git a/src/app/admin/permissions/[id]/rank-edit-client.tsx b/src/app/admin/permissions/[id]/rank-edit-client.tsx index 35617393..cd120f37 100644 --- a/src/app/admin/permissions/[id]/rank-edit-client.tsx +++ b/src/app/admin/permissions/[id]/rank-edit-client.tsx @@ -12,6 +12,7 @@ import { Terminal, UserCheck, } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useMemo, useState } from "react"; import { toast } from "sonner"; import { @@ -45,6 +46,7 @@ export function RankEditClient({ allCmsPermissions, }: RankEditorProps) { const { run, isPending } = useServerAction(); + const t = useTranslations("pages.admin.permissions.id.rankEditClient"); const [general, setGeneral] = useState({ rank_name: initialRank.rank_name, @@ -89,7 +91,7 @@ export function RankEditClient({ ]), ); run(() => saveRankAction({ id: initialRank.id, fields }), { - successMessage: "Rank saved", + successMessage: t("rankSaved"), }); } @@ -107,11 +109,11 @@ export function RankEditClient({ }); if (!r.ok) { setCmsPermSlugs(prev); - toast.error("Failed"); + toast.error(t("failed")); } } catch { setCmsPermSlugs(prev); - toast.error("Failed"); + toast.error(t("failed")); } } @@ -195,7 +197,7 @@ export function RankEditClient({ setSectionAll(filtered, "1"); }} > - All On + {t("allOn")} )} @@ -262,11 +264,14 @@ export function RankEditClient({ )}

- {general.rank_name || `Rank ${initialRank.id}`} + {general.rank_name || t("rankFallback", { id: initialRank.id })}

- Rank #{initialRank.id} · Level {initialRank.level} ·{" "} - {totalUsers} users + {t("rankMeta", { + id: initialRank.id, + level: initialRank.level, + count: totalUsers, + })}

@@ -275,28 +280,31 @@ export function RankEditClient({ - General + {t("tabGeneral")} - Emulator ({enabledCount}) + {" "} + {t("tabEmulator", { count: enabledCount })} - CMS ( - {cmsPermSlugs.length}) + {" "} + {t("tabCms", { count: cmsPermSlugs.length })} + + + {t("tabUsers", { count: totalUsers })} - Users ({totalUsers}) {/* ═══ General ═══ */} - Rank Settings + {t("rankSettings")}
- + @@ -305,7 +313,7 @@ export function RankEditClient({ />
- + @@ -314,7 +322,7 @@ export function RankEditClient({ />
- + @@ -323,7 +331,7 @@ export function RankEditClient({ />
- +

- Hide from public + {t("hideFromPublic")}

- Record to audit log + {t("recordToAuditLog")}

- +
- +
- +
- +
- + )} - Save + {t("save")}
@@ -445,10 +453,13 @@ export function RankEditClient({ - Users with this rank + {t("usersWithThisRank")} {totalUsers > users.length && ( - (showing {users.length} of {totalUsers}) + {t("showingUsers", { + shown: users.length, + total: totalUsers, + })} )} @@ -456,7 +467,7 @@ export function RankEditClient({ {users.length === 0 ? (

- No users with this rank. + {t("noUsersWithThisRank")}

) : (
@@ -490,7 +501,10 @@ export function RankEditClient({

- {enabledCount} of {totalCount} permissions enabled + {t("permissionsEnabled", { + enabled: enabledCount, + total: totalCount, + })}

@@ -500,7 +514,7 @@ export function RankEditClient({ className="h-7 text-xs" onClick={() => setAllPerms("1")} > - Enable All + {t("enableAll")}
{" "} - Off + {t("off")} {" "} - On + {t("on")} {" "} - Owner + {t("owner")}
- - + + {cats.other.length > 0 && ( - + )}
@@ -547,7 +573,7 @@ export function RankEditClient({ ) : ( )} - Save Rank + {t("saveRank")}
@@ -663,10 +689,13 @@ export function RankEditClient({ - Users with this rank + {t("usersWithThisRank")} {totalUsers > users.length && ( - (showing {users.length} of {totalUsers}) + {t("showingUsers", { + shown: users.length, + total: totalUsers, + })} )} @@ -674,7 +703,7 @@ export function RankEditClient({ {users.length === 0 ? (

- No users with this rank. + {t("noUsersWithThisRank")}

) : (
diff --git a/src/app/admin/prefixes/prefixes-client.tsx b/src/app/admin/prefixes/prefixes-client.tsx index c9703eda..1c19485d 100644 --- a/src/app/admin/prefixes/prefixes-client.tsx +++ b/src/app/admin/prefixes/prefixes-client.tsx @@ -1,6 +1,7 @@ "use client"; import { Pencil, Plus, Save, Search, Trash2 } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { addBlacklistWord as addBlacklistWordAction, @@ -35,6 +36,7 @@ import { type UserPrefix, } from "./prefix-model"; export function PrefixesClient({ canEdit }: { canEdit: boolean }) { + const t = useTranslations("pages.admin.prefixes.prefixesClient"); const { run, isPending } = useServerAction(); const { confirm, dialog: confirmDialog } = useConfirmDialog(); const [prefixes, setPrefixes] = useState([]); @@ -93,14 +95,14 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { const handleDeletePrefix = async (id: number) => { const ok = await confirm({ - title: "Delete prefix", - description: "Are you sure you want to delete this prefix?", - confirmLabel: "Delete", - cancelLabel: "Cancel", + title: t("confirmDeleteTitle"), + description: t("confirmDeleteDesc"), + confirmLabel: t("confirmDelete"), + cancelLabel: t("cancel"), }); if (!ok) return; run(() => deletePrefixAction({ id }), { - successMessage: "Prefix deleted!", + successMessage: t("successPrefixDeleted"), onSuccess: () => { fetchPrefixes(); }, @@ -126,7 +128,9 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { ? updatePrefix({ id: editId, ...fields }) : createPrefix({ username: data.username, ...fields }), { - successMessage: editId ? "Prefix updated!" : "Prefix created!", + successMessage: editId + ? t("successPrefixUpdated") + : t("successPrefixCreated"), onSuccess: () => { void fetchPrefixes(); resolve(true); @@ -150,7 +154,7 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { const handleAddBlacklistWord = async () => { if (!newWord.trim()) return; run(() => addBlacklistWordAction({ word: newWord.trim() }), { - successMessage: "Word added to blacklist!", + successMessage: t("successWordAdded"), onSuccess: () => { setNewWord(""); fetchBlacklist(); @@ -160,7 +164,7 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { const handleRemoveBlacklistWord = async (id: number) => { run(() => removeBlacklistWordAction({ id }), { - successMessage: "Word removed from blacklist!", + successMessage: t("successWordRemoved"), onSuccess: () => { fetchBlacklist(); }, @@ -174,7 +178,7 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { settings: settings as unknown as Record, }), { - successMessage: "Settings saved!", + successMessage: t("successSettingsSaved"), }, ); }; @@ -191,9 +195,9 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) { - User Prefixes - Blacklist - Settings + {t("tabUserPrefixes")} + {t("tabBlacklist")} + {t("tabSettings")} diff --git a/src/app/admin/sounds/sounds-client.tsx b/src/app/admin/sounds/sounds-client.tsx index c4b4b763..efb1626a 100644 --- a/src/app/admin/sounds/sounds-client.tsx +++ b/src/app/admin/sounds/sounds-client.tsx @@ -10,6 +10,7 @@ import { Trash2, Upload, } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useMemo, useRef, useState } from "react"; import { toast } from "sonner"; @@ -82,6 +83,7 @@ export function SoundsClient({ canEdit: boolean; baseUrl: string; }) { + const t = useTranslations("pages.admin.sounds.soundsClient"); const router = useRouter(); const { run, isPending } = useServerAction(); @@ -122,8 +124,8 @@ export function SoundsClient({ if (!deleting) return; const id = deleting.id; run(() => deleteSoundtrack({ id }), { - successMessage: `Song disk #${id} deleted.`, - errorMessage: `Unable to delete song disk #${id}.`, + successMessage: t("deleteSuccess", { id }), + errorMessage: t("deleteError", { id }), onSuccess: () => { invalidateSongPickerCache(); setDeleting(null); @@ -140,7 +142,7 @@ export function SoundsClient({ setQuery(e.target.value)} - placeholder="Search by name, author, code or id…" + placeholder={t("searchPlaceholder")} className="pl-8" />
@@ -152,12 +154,14 @@ export function SoundsClient({ - All ({soundtracks.length}) + + {t("filterAll", { count: soundtracks.length })} + - Single MP3s ({counts.single}) + {t("filterSingle", { count: counts.single })} - Composition ({counts.composition}) + {t("filterComposition", { count: counts.composition })} diff --git a/src/components/admin/catalog/catalog-items-table/add-item-form-fields.tsx b/src/components/admin/catalog/catalog-items-table/add-item-form-fields.tsx index dce7dfc0..c398dc48 100644 --- a/src/components/admin/catalog/catalog-items-table/add-item-form-fields.tsx +++ b/src/components/admin/catalog/catalog-items-table/add-item-form-fields.tsx @@ -1,5 +1,6 @@ "use client"; +// import { useTranslations } from "next-intl"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { diff --git a/src/components/admin/catalog/catalog-items-table/catalog-items-table.tsx b/src/components/admin/catalog/catalog-items-table/catalog-items-table.tsx index 9a03ee4f..36d0c8c5 100644 --- a/src/components/admin/catalog/catalog-items-table/catalog-items-table.tsx +++ b/src/components/admin/catalog/catalog-items-table/catalog-items-table.tsx @@ -179,6 +179,7 @@ export function CatalogItemsTable({ const router = useRouter(); const bulk = useTranslations("pages.admin.catalog.bulk"); const discovery = useTranslations("pages.admin.catalog.discovery"); + const t = useTranslations("pages.admin.catalog.catalogItemsTable.catalogItemsTable"); const [bulkRefreshFrom, setBulkRefreshFrom] = useState< CatalogItemData[] | null >(null); @@ -818,7 +819,7 @@ export function CatalogItemsTable({
- Items ({items.length}) + {t("itemsCount", { count: items.length })} {hasDiscoveryFilter && filteredItems.length !== items.length && (