feat: replace all hardcoded en-US and $ formatting with locale-aware helpers

This commit is contained in:
Your Name
2026-02-17 23:39:31 +02:00
parent fe0b20f247
commit 519bdabe61
7 changed files with 23 additions and 53 deletions

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue'
import { Calendar, ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { getLocaleCode } from '../utils/locale'
interface Props {
modelValue: string
@@ -29,7 +30,7 @@ const displayText = computed(() => {
if (!props.modelValue) return null
const [y, m, d] = props.modelValue.split('-').map(Number)
const date = new Date(y, m - 1, d)
return date.toLocaleDateString('en-US', {
return date.toLocaleDateString(getLocaleCode(), {
month: 'short',
day: 'numeric',
year: 'numeric',
@@ -38,7 +39,7 @@ const displayText = computed(() => {
const viewMonthLabel = computed(() => {
const date = new Date(viewYear.value, viewMonth.value, 1)
return date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
return date.toLocaleDateString(getLocaleCode(), { month: 'long', year: 'numeric' })
})
// ── Today helpers ───────────────────────────────────────────────────

View File

@@ -94,6 +94,7 @@ import {
import { Clock } from 'lucide-vue-next'
import { useEntriesStore } from '../stores/entries'
import { useProjectsStore } from '../stores/projects'
import { formatDateLong } from '../utils/locale'
import type { TimeEntry } from '../stores/entries'
// Register Chart.js components
@@ -117,12 +118,7 @@ const greeting = computed(() => {
// Formatted date
const formattedDate = computed(() => {
return new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})
return formatDateLong(new Date().toISOString())
})
// Empty state check

View File

@@ -231,6 +231,7 @@ import AppSelect from '../components/AppSelect.vue'
import AppDatePicker from '../components/AppDatePicker.vue'
import { useEntriesStore, type TimeEntry } from '../stores/entries'
import { useProjectsStore } from '../stores/projects'
import { formatDate } from '../utils/locale'
const entriesStore = useEntriesStore()
const projectsStore = useProjectsStore()
@@ -316,16 +317,6 @@ function formatDuration(seconds: number): string {
return `${minutes}m`
}
// Format date
function formatDate(dateString: string): string {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
}
// Apply filters
function applyFilters() {
entriesStore.fetchEntries(startDate.value || undefined, endDate.value || undefined)

View File

@@ -54,7 +54,7 @@
{{ formatDate(invoice.date) }}
</td>
<td class="px-4 py-3 text-right text-[0.75rem] font-mono text-accent-text">
${{ invoice.total.toFixed(2) }}
{{ formatCurrency(invoice.total) }}
</td>
<td class="px-4 py-3 text-center">
<span
@@ -194,19 +194,19 @@
<div class="bg-bg-inset rounded-lg p-4">
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Subtotal:</span>
<span class="font-mono">${{ calculateSubtotal().toFixed(2) }}</span>
<span class="font-mono">{{ formatCurrency(calculateSubtotal()) }}</span>
</div>
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Tax ({{ createForm.tax_rate }}%):</span>
<span class="font-mono">${{ calculateTax().toFixed(2) }}</span>
<span class="font-mono">{{ formatCurrency(calculateTax()) }}</span>
</div>
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Discount:</span>
<span class="font-mono">-${{ createForm.discount.toFixed(2) }}</span>
<span class="font-mono">-{{ formatCurrency(createForm.discount) }}</span>
</div>
<div class="flex justify-between text-text-primary font-medium text-[0.8125rem] pt-2 border-t border-border-subtle">
<span>Total:</span>
<span class="font-mono text-accent-text">${{ calculateTotal().toFixed(2) }}</span>
<span class="font-mono text-accent-text">{{ formatCurrency(calculateTotal()) }}</span>
</div>
</div>
@@ -266,19 +266,19 @@
<div class="border-t border-border-subtle pt-4">
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Subtotal:</span>
<span class="font-mono">${{ (selectedInvoice?.subtotal || 0).toFixed(2) }}</span>
<span class="font-mono">{{ formatCurrency(selectedInvoice?.subtotal || 0) }}</span>
</div>
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Tax ({{ selectedInvoice?.tax_rate || 0 }}%):</span>
<span class="font-mono">${{ (selectedInvoice?.tax_amount || 0).toFixed(2) }}</span>
<span class="font-mono">{{ formatCurrency(selectedInvoice?.tax_amount || 0) }}</span>
</div>
<div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Discount:</span>
<span class="font-mono">-${{ (selectedInvoice?.discount || 0).toFixed(2) }}</span>
<span class="font-mono">-{{ formatCurrency(selectedInvoice?.discount || 0) }}</span>
</div>
<div class="flex justify-between text-text-primary font-medium text-[0.8125rem] pt-2 border-t border-border-subtle">
<span>Total:</span>
<span class="font-mono text-accent-text">${{ (selectedInvoice?.total || 0).toFixed(2) }}</span>
<span class="font-mono text-accent-text">{{ formatCurrency(selectedInvoice?.total || 0) }}</span>
</div>
</div>
@@ -339,6 +339,7 @@ import { useInvoicesStore, type Invoice } from '../stores/invoices'
import { useClientsStore } from '../stores/clients'
import { useToastStore } from '../stores/toast'
import { generateInvoicePdf } from '../utils/invoicePdf'
import { formatDate, formatCurrency } from '../utils/locale'
const invoicesStore = useInvoicesStore()
const clientsStore = useClientsStore()
@@ -369,17 +370,6 @@ function getClientName(clientId: number): string {
return client?.name || 'Unknown Client'
}
// Format date
function formatDate(dateString: string): string {
if (!dateString) return '-'
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
}
// Calculate subtotal (simplified - would need line items in a real app)
function calculateSubtotal(): number {
return 0

View File

@@ -24,7 +24,7 @@
<div class="flex items-start justify-between">
<div>
<h3 class="text-[0.8125rem] font-semibold text-text-primary">{{ project.name }}</h3>
<p class="text-xs text-text-secondary mt-0.5">{{ getClientName(project.client_id) }} · ${{ project.hourly_rate.toFixed(2) }}/hr</p>
<p class="text-xs text-text-secondary mt-0.5">{{ getClientName(project.client_id) }} · {{ formatCurrency(project.hourly_rate) }}/hr</p>
</div>
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-100">
<button
@@ -200,6 +200,7 @@ import AppSelect from '../components/AppSelect.vue'
import { useProjectsStore, type Project } from '../stores/projects'
import { useClientsStore } from '../stores/clients'
import { useSettingsStore } from '../stores/settings'
import { formatCurrency } from '../utils/locale'
const colorPresets = ['#D97706', '#3B82F6', '#8B5CF6', '#EC4899', '#10B981', '#EF4444', '#06B6D4', '#6B7280']

View File

@@ -40,7 +40,7 @@
</div>
<div>
<p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1">Earnings</p>
<p class="text-[1.25rem] font-[family-name:var(--font-heading)] text-accent-text font-medium">${{ reportData.totalEarnings?.toFixed(2) || '0.00' }}</p>
<p class="text-[1.25rem] font-[family-name:var(--font-heading)] text-accent-text font-medium">{{ formatCurrency(reportData.totalEarnings || 0) }}</p>
</div>
<div>
<p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1">Projects</p>
@@ -80,7 +80,7 @@
</div>
<div class="flex items-center gap-4">
<span class="text-[0.75rem] font-mono text-accent-text">{{ formatHours(projectData.total_seconds) }}</span>
<span class="text-[0.75rem] font-mono text-text-secondary">${{ ((projectData.total_seconds / 3600) * getProjectRate(projectData.project_id)).toFixed(2) }}</span>
<span class="text-[0.75rem] font-mono text-text-secondary">{{ formatCurrency((projectData.total_seconds / 3600) * getProjectRate(projectData.project_id)) }}</span>
</div>
</div>
<div class="w-full bg-bg-elevated rounded-full h-[2px]">
@@ -120,6 +120,7 @@ import {
} from 'chart.js'
import { useEntriesStore } from '../stores/entries'
import { useProjectsStore } from '../stores/projects'
import { formatCurrency } from '../utils/locale'
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)

View File

@@ -87,7 +87,7 @@
</div>
<div class="text-right">
<p class="text-[0.75rem] font-mono text-text-primary">{{ formatDuration(entry.duration) }}</p>
<p class="text-[0.6875rem] text-text-tertiary">{{ formatDate(entry.start_time) }}</p>
<p class="text-[0.6875rem] text-text-tertiary">{{ formatDateTime(entry.start_time) }}</p>
</div>
</div>
</div>
@@ -110,6 +110,7 @@ import { useEntriesStore } from '../stores/entries'
import { useToastStore } from '../stores/toast'
import { Timer as TimerIcon } from 'lucide-vue-next'
import AppSelect from '../components/AppSelect.vue'
import { formatDateTime } from '../utils/locale'
const timerStore = useTimerStore()
const projectsStore = useProjectsStore()
@@ -200,17 +201,6 @@ function formatDuration(seconds: number): string {
return `${minutes}m`
}
// Format date
function formatDate(dateString: string): string {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
// Load data on mount
onMounted(async () => {
await Promise.all([