Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
47d8901
Add localization types and utilities
Cocodrulo Apr 12, 2026
fa26b01
Use localized time/date helpers in TopBar
Cocodrulo Apr 12, 2026
2aa3569
Localize TagManager strings
Cocodrulo Apr 12, 2026
a0adc4b
Localize SOPAgreementOverlay texts
Cocodrulo Apr 12, 2026
abcc5b2
Localize labels in ReportMetadata component
Cocodrulo Apr 12, 2026
2394a1d
Localize ReportEditorHeader strings
Cocodrulo Apr 12, 2026
d96407b
Localize Pagination component strings
Cocodrulo Apr 12, 2026
6e576b2
Localize strings in MugshotCamera component
Cocodrulo Apr 12, 2026
d4e4a46
Localize LoginOverlay strings
Cocodrulo Apr 12, 2026
8574d48
Localize InvolvedPersons component strings
Cocodrulo Apr 12, 2026
0551156
Half translations added
Cocodrulo Apr 12, 2026
991cf54
Merge branch 'main' of https://github.com/Project-Sloth/ps-mdt into l…
Cocodrulo Apr 15, 2026
4bd4593
Localize EvidenceManager component
Cocodrulo Apr 15, 2026
770ac6c
Support numeric replacements in localization
Cocodrulo Apr 15, 2026
024c7cd
Localize ContentArea strings
Cocodrulo Apr 15, 2026
e9b4481
Localize ChargeType component UI and currency
Cocodrulo Apr 15, 2026
ef6a0fb
Add getLocalizedCurrency helper
Cocodrulo Apr 15, 2026
12a8068
Localize VictimsManager default title
Cocodrulo Apr 15, 2026
9099727
Localize VehiclesManager UI strings
Cocodrulo Apr 15, 2026
8822ca3
Localize TagsManager UI strings
Cocodrulo Apr 15, 2026
291fe9c
Localize SuspectsManager strings
Cocodrulo Apr 15, 2026
b7c69b4
Add translations for evidence, vehicles, and managers
Cocodrulo Apr 15, 2026
31e7548
Localize ReportHeader strings
Cocodrulo Apr 15, 2026
f0c0ddd
Localize ReportEditorToolbar titles
Cocodrulo Apr 15, 2026
44d3e11
Localize text in PersonSearchModal
Cocodrulo Apr 15, 2026
4990a48
Use localization in PersonnelSection add button
Cocodrulo Apr 15, 2026
6db0dcc
Localize ImageUploadModal strings
Cocodrulo Apr 15, 2026
53ccb7f
Localize EvidenceManager strings
Cocodrulo Apr 15, 2026
4f541b8
Localize CollabPresenceBar label
Cocodrulo Apr 15, 2026
ddf9915
Localize ManagementVisibility and ChargesManager
Cocodrulo Apr 15, 2026
c3c9adb
Localize ManagementTracking strings
Cocodrulo Apr 15, 2026
e5ab292
Localize ManagementTemplates strings
Cocodrulo Apr 15, 2026
a096005
Localize ManagementTags component
Cocodrulo Apr 15, 2026
37ade91
Localize ManagementSOP strings
Cocodrulo Apr 15, 2026
0694d1d
Localize management permissions UI
Cocodrulo Apr 15, 2026
eeea971
Localize management licenses UI strings
Cocodrulo Apr 15, 2026
e4ff957
Localize ManagementJailFines UI strings
Cocodrulo Apr 15, 2026
047875e
Localize ManagementFTO component strings
Cocodrulo Apr 15, 2026
2958fb9
Localize ManagementColors strings
Cocodrulo Apr 15, 2026
c475390
Localize ManagementBulletins strings
Cocodrulo Apr 15, 2026
17c1fa9
Localize ManagementAwards component strings
Cocodrulo Apr 15, 2026
300a60e
Localize ManagementActivity strings
Cocodrulo Apr 15, 2026
702a545
Localize PermissionRole strings
Cocodrulo Apr 15, 2026
8b6a061
Localize ReportItem UI strings
Cocodrulo Apr 15, 2026
0504f28
Localize Weapons.svelte UI strings
Cocodrulo Apr 15, 2026
f8c1fdc
Localize Warrants page strings
Cocodrulo Apr 15, 2026
0378ffe
Localize Vehicles page strings
Cocodrulo Apr 15, 2026
989fcf0
Localize SOP page strings
Cocodrulo Apr 15, 2026
4f32615
Localize Settings page strings
Cocodrulo Apr 15, 2026
fc3d41b
Localize Roster.svelte UI strings
Cocodrulo Apr 15, 2026
897c069
Localize Reports page strings and dates
Cocodrulo Apr 15, 2026
d76622d
Localize ReportEditor strings
Cocodrulo Apr 15, 2026
f74bf47
Localize PPR page UI strings
Cocodrulo Apr 15, 2026
9b59530
Add English translations for MDT features
Cocodrulo Apr 15, 2026
7aac38c
Localize map UI strings
Cocodrulo Apr 16, 2026
43cd7ab
Localize IA.svelte labels and date/time
Cocodrulo Apr 16, 2026
3168599
Localize Evidence page strings
Cocodrulo Apr 16, 2026
ed47d15
Localize Dashboard strings and dates
Cocodrulo Apr 16, 2026
3e6ba97
Localize ComplaintForm strings
Cocodrulo Apr 16, 2026
4f1aa2a
Use i18n keys for submit button
Cocodrulo Apr 16, 2026
8dc49c2
Localize CivilianView strings and currency
Cocodrulo Apr 16, 2026
5f5700a
Localize Citizens.svelte UI strings
Cocodrulo Apr 16, 2026
5b978a6
Localize Charges page UI strings
Cocodrulo Apr 16, 2026
7c81824
Localize Cases page strings and date/time
Cocodrulo Apr 16, 2026
5868123
Localize Cameras page strings
Cocodrulo Apr 16, 2026
8d5b381
Localize BOLOs page strings
Cocodrulo Apr 16, 2026
c0c270f
Localize Bodycams page strings
Cocodrulo Apr 16, 2026
037c255
Localize Awards page strings
Cocodrulo Apr 16, 2026
7e73dc7
Localize WarrantReview UI strings and dates
Cocodrulo Apr 16, 2026
9cf3d93
Localize New Legal Document modal
Cocodrulo Apr 16, 2026
64afa10
Localize CourtOrders page strings
Cocodrulo Apr 16, 2026
f3322f5
Localize CourtCases.svelte UI strings
Cocodrulo Apr 16, 2026
ea8c4ea
Move translations to public and improve i18n
Cocodrulo Apr 16, 2026
b7124a9
Add Spanish (es) translations and localization
Cocodrulo Apr 16, 2026
008ab38
Use localization for UI labels and tabs
Cocodrulo Apr 18, 2026
5f5d25c
Use localized date/time and add i18n keys
Cocodrulo Apr 18, 2026
e6d5018
Update translations and UI strings
Cocodrulo Apr 18, 2026
8a78046
Migrate localization to Svelte store & add locale support
Cocodrulo Apr 18, 2026
7b8e9fe
Fix locale handling and translations path
Cocodrulo Apr 18, 2026
83be32c
Refactor constants to dynamic getters and i18n keys
Cocodrulo Apr 18, 2026
bcf0b3d
Remove hardcoded Spanish locale
Cocodrulo Apr 18, 2026
8b26759
Localize management tab labels
Cocodrulo May 3, 2026
aec4239
Localize 'Search Suspects' title and add translations
Cocodrulo May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/keys.lua
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ function OpenMDT()

MDTOpen = true

SendNUI('setLocale', { locale = Config.Locale })
SendNUI('setVisible', { visible = true, debugMode = Config.Debug })

if isCivilian then
Expand Down
1 change: 1 addition & 0 deletions config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ps = exports.ps_lib:init()
-- Basic Settings
Config.Debug = false -- Enable/disable debug mode (boolean)
Config.OnlyShowOnDuty = true -- Only allow the MDT to be opened when on duty (boolean)
Config.Locale = "en"

-- Civilian Access Settings
Config.CivilianAccess = {
Expand Down
1,928 changes: 1,928 additions & 0 deletions web/public/translations/en.json

Large diffs are not rendered by default.

1,928 changes: 1,928 additions & 0 deletions web/public/translations/es.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions web/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { queryClient } from "./utils/query-client";
import { onMount } from "svelte";
import { setupInputDebug } from "./utils/debugInputBlocker";
import { setLocale } from "./utils/localization.svelte";

let cleanupInputDebug: (() => void) | undefined;
let showComplaintForm = $state(false);
Expand All @@ -16,12 +17,16 @@
if (import.meta.env && import.meta.env.DEV) {
cleanupInputDebug = setupInputDebug();
}
setLocale();

// Listen for complaint form trigger (outside VisibilityProvider so it works for civilians)
const handleMessage = (event: MessageEvent) => {
if (event.data?.action === 'showComplaintForm') {
showComplaintForm = true;
}
if (event.data?.action === 'setLocale') {
setLocale(event.data.data.locale);
}
};
window.addEventListener('message', handleMessage);

Expand Down
17 changes: 9 additions & 8 deletions web/src/components/ChargeType.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { _L, getLocalizedCurrency } from "@/utils/localization.svelte";
import type { Charge } from "./../interfaces/ICharges";

interface Props {
Expand Down Expand Up @@ -114,7 +115,7 @@
}

function formatFine(value: number): string {
return `$${value.toLocaleString()}`;
return getLocalizedCurrency(value);
}

function formatTime(value: number): string {
Expand All @@ -139,11 +140,11 @@
<div class="category-group">
<div class="category-label">{category}</div>
<div class="table-header">
<span class="col-code">Code</span>
<span class="col-label">Charge</span>
<span class="col-desc">Description</span>
<span class="col-fine">Fine</span>
<span class="col-time">Time</span>
<span class="col-code">{_L("charges.code")}</span>
<span class="col-label">{_L("charges.charge")}</span>
<span class="col-desc">{_L("charges.description")}</span>
<span class="col-fine">{_L("charges.fine")}</span>
<span class="col-time">{_L("charges.time")}</span>
{#if isEditing}
<span class="col-actions"></span>
{/if}
Expand Down Expand Up @@ -193,14 +194,14 @@
onclick={() => saveEdit(charge)}
disabled={isSaving}
>
{isSaving ? "..." : "Save"}
{isSaving ? "..." : _L("charges.save")}
</button>
<button
class="btn-cancel"
onclick={cancelEdit}
disabled={isSaving}
>
Cancel
{_L("charges.cancel")}
</button>
</span>
</div>
Expand Down
12 changes: 6 additions & 6 deletions web/src/components/ContentArea.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { PLACEHOLDER_COMPONENTS, type ComponentId } from "../constants";
import { PLACEHOLDER_COMPONENTS, type ComponentId } from "../constants/index.svelte";
import type { AuthService } from "../services/authService.svelte";
import { fetchNui } from "../utils/fetchNui";
import { NUI_EVENTS } from "../constants/nuiEvents";
Expand Down Expand Up @@ -35,6 +35,7 @@
import LegalDocuments from "../pages/doj/LegalDocuments.svelte";
import type { createInstanceStateService } from "../services/instanceStateService.svelte";
import type { createTabService } from "../services/tabService.svelte";
import { _L } from "@/utils/localization.svelte";

interface Props {
authService: AuthService;
Expand Down Expand Up @@ -171,14 +172,13 @@
<div class="denied-overlay">
<div class="denied-card">
<span class="material-icons denied-icon">lock</span>
<span class="denied-title">Permission Denied</span>
<span class="denied-title">{_L("contentArea.permissionDenied")}</span>
<span class="denied-desc">
You do not have permission to access <strong>{getPageLabel(activeComponent)}</strong>.
Contact a supervisor to request access.
{_L("contentArea.permissionDeniedDesc", ["page", getPageLabel(activeComponent)])}
</span>
<button class="denied-btn" onclick={() => tabService.setActiveTab("Dashboard")}>
<span class="material-icons denied-btn-icon">arrow_back</span>
Back to Dashboard
{_L("contentArea.backToDashboard")}
</button>
</div>
</div>
Expand Down Expand Up @@ -242,7 +242,7 @@
{:else}
<PlaceholderContent
componentId={activeComponent}
message="Component not found"
message={_L("contentArea.componentNotFound")}
/>
{/if}
{/if}
Expand Down
45 changes: 23 additions & 22 deletions web/src/components/EvidenceManager.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { EVIDENCE_TYPES } from "../constants";
import { _L } from "@/utils/localization.svelte";
import { EVIDENCE_TYPES } from "../constants/index.svelte";
import type { Report, Evidence } from "../interfaces/IReportEditor";

export let report: Report;
Expand Down Expand Up @@ -67,15 +68,15 @@

<section class="evidence-manager" aria-label="Evidence management">
<div class="evidence-header">
<h3 class="section-title">Evidence ({report.evidence.length})</h3>
<h3 class="section-title">_L("evidence.title") ({report.evidence.length})</h3>
<button
type="button"
on:click={toggleEvidenceForm}
class="add-evidence-btn"
aria-expanded={showEvidenceForm}
aria-controls="evidence-form"
>
{showEvidenceForm ? "Cancel" : "Add Evidence"}
{showEvidenceForm ? _L("evidence.cancel") : _L("evidence.addEvidence")}
</button>
</div>

Expand All @@ -90,7 +91,7 @@
<div class="form-row">
<div class="form-field">
<label for="evidence-title" class="form-label"
>Title *</label
>_L("evidence.title") *</label
>
<input
id="evidence-title"
Expand All @@ -101,34 +102,34 @@
aria-describedby="evidence-title-help"
/>
<div id="evidence-title-help" class="sr-only">
Enter a descriptive title for the evidence
_L("evidence.titleHelp")
</div>
</div>

<div class="form-field">
<label for="evidence-type" class="form-label">Type *</label>
<label for="evidence-type" class="form-label">_L("evidence.type") *</label>
<select
id="evidence-type"
bind:value={newEvidence.type}
class="form-select"
required
aria-describedby="evidence-type-help"
>
<option value="">Select type...</option>
<option value="">_L("evidence.selectType")</option>
{#each EVIDENCE_TYPES as type}
<option value={type}>{type}</option>
{/each}
</select>
<div id="evidence-type-help" class="sr-only">
Select the type of evidence
_L("evidence.typeHelp")
</div>
</div>
</div>

<div class="form-row">
<div class="form-field">
<label for="evidence-serial" class="form-label"
>Serial Number</label
>_L("evidence.serial")</label
>
<input
id="evidence-serial"
Expand All @@ -138,13 +139,13 @@
aria-describedby="evidence-serial-help"
/>
<div id="evidence-serial-help" class="sr-only">
Enter serial number if applicable
_L("evidence.serialHelp")
</div>
</div>
</div>

<div class="form-field">
<label for="evidence-notes" class="form-label">Notes</label>
<label for="evidence-notes" class="form-label">_L("evidence.notes")</label>
<textarea
id="evidence-notes"
bind:value={newEvidence.notes}
Expand All @@ -153,7 +154,7 @@
aria-describedby="evidence-notes-help"
></textarea>
<div id="evidence-notes-help" class="sr-only">
Additional notes about the evidence
_L("evidence.notesHelp")
</div>
</div>

Expand All @@ -164,20 +165,20 @@
class="save-btn"
aria-describedby="save-btn-help"
>
{editingEvidence ? "Update Evidence" : "Add Evidence"}
{editingEvidence ? _L("evidence.updateEvidence") : _L("evidence.addEvidence")}
</button>
<button
type="button"
on:click={toggleEvidenceForm}
class="cancel-btn"
>
Cancel
_L("evidence.cancel")
</button>
</div>
<div id="save-btn-help" class="sr-only">
{formValid
? "Form is valid and ready to submit"
: "Please fill in required fields"}
? _L("evidence.saveBtnHelp")
: _L("evidence.requiredFields")}
</div>
</form>
{/if}
Expand All @@ -196,7 +197,7 @@

{#if evidence.serial}
<div class="evidence-detail">
<span class="detail-label">Serial:</span>
<span class="detail-label">_L("evidence.serial")</span>
<span class="detail-value"
>{evidence.serial}</span
>
Expand All @@ -205,7 +206,7 @@

{#if evidence.notes}
<div class="evidence-detail">
<span class="detail-label">Notes:</span>
<span class="detail-label">_L("evidence.notes")</span>
<span class="detail-value"
>{evidence.notes}</span
>
Expand All @@ -219,7 +220,7 @@
aria-label="Evidence images"
>
<h5 class="images-title">
Images ({evidence.images.length})
{_L("evidence.images", ["count", evidence.images.length])}
</h5>
<div class="images-grid">
{#each evidence.images as image, imageIndex}
Expand Down Expand Up @@ -257,22 +258,22 @@
class="edit-btn"
aria-label="Edit evidence: {evidence.title}"
>
Edit
_L("evidence.editEvidence")
</button>
<button
type="button"
on:click={() => removeEvidence(evidence.id)}
class="remove-btn"
aria-label="Remove evidence: {evidence.title}"
>
Remove
_L("evidence.removeEvidence")
</button>
</div>
</article>
{/each}
</div>
{:else}
<p class="no-evidence">No evidence added</p>
<p class="no-evidence">_L("evidence.noEvidence")</p>
{/if}
</section>

Expand Down
12 changes: 8 additions & 4 deletions web/src/components/InstanceTabButton.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { ComponentId, MDTTab } from "../constants";
import { MDT_TABS } from "../constants";
import type { ComponentId, MDTTab } from "../constants/index.svelte";
import { getMDTTabs } from "../constants/index.svelte";
import type { TabInstance } from "../services/tabService.svelte.ts";

interface Props {
Expand All @@ -14,7 +14,7 @@
$props();

let tabIcon = $derived(
MDT_TABS.find((t) => t.name === instance.currentTab)?.icon || "tab",
getMDTTabs().find((t) => t.name === instance.currentTab)?.icon || "tab",
);

function handleInstanceClick(): void {
Expand All @@ -35,6 +35,10 @@
onInstanceClose(instance.id, syntheticEvent);
}
}

function getTabLabel(tabName: MDTTab): string {
return getMDTTabs().find((t) => t.name === tabName)?.label || tabName;
}
</script>

<div class="instance-tab-container">
Expand All @@ -45,7 +49,7 @@
aria-label="Switch to {instance.currentTab}"
>
<span class="tab-icon material-icons">{tabIcon}</span>
<span class="tab-name">{instance.currentTab}</span>
<span class="tab-name">{getTabLabel(instance.currentTab)}</span>
{#if showCloseButton}
<span
class="close-btn"
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/InstanceTabs.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import type { ComponentId, MDTTab } from "../constants";
import type { ComponentId, MDTTab } from "../constants/index.svelte";
import InstanceTabButton from "./InstanceTabButton.svelte";
import type {
TabInstance,
Expand Down
Loading