i18n(mod-tools): route every label/title/placeholder through LocalizeText

The ModTools template refresh introduced ~80 hardcoded English strings
(labels, placeholders, tooltips, empty-state copy, button text). Move
every one of them onto the modtools.* namespace and read via
LocalizeText so the panels translate alongside the rest of the client.

UITexts.example (versioned template) extended with the full set:

  modtools.window.*            Launcher box (toolbar item, tools,
                               selected-user state, ticket count)
  modtools.userinfo.*          User info card — already had the
                               legacy modtools.userinfo.{userName,
                               cfhCount, …} keys from before; added
                               refresh tooltip, presence pill labels
                               (in_room / online / offline with
                               matching .title tooltips), section
                               headings, action button labels, stat
                               card labels
  modtools.roominfo.*          Room info card — title, refresh, loading,
                               owner pill (here/away + tooltips), stat
                               labels, action buttons, moderate panel
                               heading + checkboxes + textarea
                               placeholder + caution/alert CTAs
  modtools.user.message.*      Send-message dialog (recipient label,
                               body label, placeholder, char counter,
                               empty state, send button)
  modtools.user.modaction.*    Mod Action form — header, sanctioning
                               label, 3-step section titles, select
                               placeholders, message label + optional
                               note, message placeholder, preview
                               heading, default/apply buttons, every
                               sendAlert error message
  modtools.user.visits.*       Room visits — title, header strip
                               heading, entry count (singular/plural),
                               empty state, column headers, visit
                               button + tooltip
  modtools.user.chatlog.*      User chatlog — title (with username
                               variant), loading state
  modtools.room.chatlog.*      Room chatlog title
  modtools.chatlog.*           Shared ChatlogView — column headers,
                               empty state, room-separator Visit/Tools
                               buttons
  modtools.tickets.*           Tickets window — title, tab labels
                               (open/mine/picked), column headers,
                               empty states, action buttons (pick/
                               handle/release), issue resolution
                               window (title, label, details heading,
                               field labels, chatlog toggle, resolve-as
                               heading, resolution buttons, release
                               back to queue), CFH chatlog title

The same 130 entries land in Nitro-Files/.../UITexts.json (runtime).
Both files validate as JSON. The runtime additions take effect on
next client reload; the template additions ship the strings to any
fresh deploy.

Notes:
  - The MOD_ACTION_DEFINITIONS sanction names ("Alert", "Mute 1h",
    "Ban 18h" …) stay hardcoded for now since they're keyed off
    server-side action IDs that don't have an existing locale key
    convention. Worth a follow-up if needed.
  - help.cfh.topic.* keys (CFH topic display names) are already in
    ExternalTexts.json and were already read via LocalizeText, so
    they didn't need changes.

typecheck + vitest 214/214 + lint:hooks all clean.
This commit is contained in:
simoleo89
2026-05-20 21:02:22 +02:00
committed by simoleo89
parent d3552a0948
commit 75815fa022
16 changed files with 291 additions and 139 deletions
@@ -43,13 +43,13 @@ export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
return (
<>
<NitroCardView className="nitro-mod-tools-handle-issue min-w-[440px] max-w-[500px]" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
<NitroCardHeaderView headerText={ `Resolving issue #${ issueId }` } onCloseClick={ () => onIssueInfoClosed(issueId) } />
<NitroCardHeaderView headerText={ LocalizeText('modtools.tickets.issue.title', [ 'issueId' ], [ issueId.toString() ]) } onCloseClick={ () => onIssueInfoClosed(issueId) } />
<NitroCardContentView className="text-black" gap={ 2 }>
{/* Issue header */}
<div className="flex items-center gap-2 bg-gradient-to-r from-amber-50 to-transparent rounded p-2 border border-amber-100">
<FaCommentDots className="text-amber-600 shrink-0" size={ 16 } />
<div className="flex flex-col grow min-w-0">
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">Issue #{ issueId }</div>
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">{ LocalizeText('modtools.tickets.issue.label', [ 'issueId' ], [ issueId.toString() ]) }</div>
<div className="font-semibold leading-tight truncate">{ GetIssueCategoryName(ticket.categoryId) }</div>
</div>
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border bg-white border-amber-200 text-amber-800">
@@ -59,19 +59,19 @@ export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
{/* Details */}
<div className="flex flex-col gap-1">
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold border-b border-zinc-200 pb-1 mb-0.5">Details</div>
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold border-b border-zinc-200 pb-1 mb-0.5">{ LocalizeText('modtools.tickets.issue.details') }</div>
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-[.8rem] m-0">
<Field label="Source">{ GetIssueCategoryName(ticket.categoryId) }</Field>
<Field label="Category">{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }</Field>
<Field label="Description">{ ticket.message }</Field>
<Field label="Caller">
<Field label={ LocalizeText('modtools.tickets.issue.field.source') }>{ GetIssueCategoryName(ticket.categoryId) }</Field>
<Field label={ LocalizeText('modtools.tickets.issue.field.category') }>{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }</Field>
<Field label={ LocalizeText('modtools.tickets.issue.field.description') }>{ ticket.message }</Field>
<Field label={ LocalizeText('modtools.tickets.issue.field.caller') }>
<button
className="font-semibold text-sky-700 hover:text-sky-900 hover:underline inline-flex items-center gap-1"
onClick={ () => openUserInfo(ticket.reporterUserId) }>
{ ticket.reporterUserName } <FaExternalLinkAlt size={ 8 } className="opacity-60" />
</button>
</Field>
<Field label="Reported">
<Field label={ LocalizeText('modtools.tickets.issue.field.reported') }>
<button
className="font-semibold text-sky-700 hover:text-sky-900 hover:underline inline-flex items-center gap-1"
onClick={ () => openUserInfo(ticket.reportedUserId) }>
@@ -83,25 +83,25 @@ export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
{/* Tools */}
<Button gap={ 1 } variant="secondary" onClick={ () => setCfhChatlogOpen(prev => !prev) }>
<FaCommentDots size={ 12 } /> { cfhChatlogOpen ? 'Close chatlog' : 'View chatlog' }
<FaCommentDots size={ 12 } /> { cfhChatlogOpen ? LocalizeText('modtools.tickets.issue.chatlog.close') : LocalizeText('modtools.tickets.issue.chatlog.view') }
</Button>
{/* Resolution buttons */}
<div className="flex flex-col gap-1.5 pt-1 border-t border-zinc-200">
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">Resolve as</div>
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">{ LocalizeText('modtools.tickets.issue.resolve.heading') }</div>
<div className="grid grid-cols-3 gap-1.5">
<Button gap={ 1 } variant="success" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED) }>
<FaCheck size={ 11 } /> Resolved
<FaCheck size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.resolved') }
</Button>
<Button gap={ 1 } variant="dark" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS) }>
<FaTrashAlt size={ 11 } /> Useless
<FaTrashAlt size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.useless') }
</Button>
<Button gap={ 1 } variant="danger" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE) }>
<FaBan size={ 11 } /> Abusive
<FaBan size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.abusive') }
</Button>
</div>
<Button gap={ 1 } variant="secondary" onClick={ releaseIssue }>
<FaSignOutAlt size={ 12 } /> Release back to queue
<FaSignOutAlt size={ 12 } /> { LocalizeText('modtools.tickets.issue.release') }
</Button>
</div>
</NitroCardContentView>