Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
327 changes: 327 additions & 0 deletions web/src/i18n/en.ts

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions web/src/i18n/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,87 @@ export function weekdayLabel(value: WeekdayValue | (string & {})): string {
return key ? i18n.t(key) : String(value);
}

// ---------------------------------------------------------------------------
// audit action (AuditPage filter + table) — AuditEvent.action, a server
// "Entity.Verb" capture-point code (#182, Task 29). This is also the single
// source of truth for the filter's option list — AuditPage no longer keeps
// its own local `actions` array (part of the #84 magic-strings debt).
// ---------------------------------------------------------------------------
export const AUDIT_ACTION_VALUES = [
"DailyEntry.Adjust", "DailyEntry.Void", "SalesOrder.Void", "Payment.Void",
"Expense.Adjust", "ExpenseCategory.Update", "InventoryItem.Adjust",
"WaterUsage.Correct", "Flock.BirdMovement", "Flock.Update", "Flock.Deplete",
"Flock.Archive", "Flock.Reactivate", "EggGrade.Update", "EggGrade.Activate",
"EggGrade.Deactivate", "User.Create", "User.Update", "User.PasswordSet",
"User.PasswordChanged", "User.FlockAssign", "User.FlockUnassign",
"Account.Export", "Product.Create", "Product.Update", "Product.Activate",
"Product.Deactivate", "EggUnitConversion.Update",
] as const;
export type AuditActionValue = (typeof AUDIT_ACTION_VALUES)[number];
const AUDIT_ACTION_KEYS = {
"DailyEntry.Adjust": "enums:auditAction.DailyEntry.Adjust",
"DailyEntry.Void": "enums:auditAction.DailyEntry.Void",
"SalesOrder.Void": "enums:auditAction.SalesOrder.Void",
"Payment.Void": "enums:auditAction.Payment.Void",
"Expense.Adjust": "enums:auditAction.Expense.Adjust",
"ExpenseCategory.Update": "enums:auditAction.ExpenseCategory.Update",
"InventoryItem.Adjust": "enums:auditAction.InventoryItem.Adjust",
"WaterUsage.Correct": "enums:auditAction.WaterUsage.Correct",
"Flock.BirdMovement": "enums:auditAction.Flock.BirdMovement",
"Flock.Update": "enums:auditAction.Flock.Update",
"Flock.Deplete": "enums:auditAction.Flock.Deplete",
"Flock.Archive": "enums:auditAction.Flock.Archive",
"Flock.Reactivate": "enums:auditAction.Flock.Reactivate",
"EggGrade.Update": "enums:auditAction.EggGrade.Update",
"EggGrade.Activate": "enums:auditAction.EggGrade.Activate",
"EggGrade.Deactivate": "enums:auditAction.EggGrade.Deactivate",
"User.Create": "enums:auditAction.User.Create",
"User.Update": "enums:auditAction.User.Update",
"User.PasswordSet": "enums:auditAction.User.PasswordSet",
"User.PasswordChanged": "enums:auditAction.User.PasswordChanged",
"User.FlockAssign": "enums:auditAction.User.FlockAssign",
"User.FlockUnassign": "enums:auditAction.User.FlockUnassign",
"Account.Export": "enums:auditAction.Account.Export",
"Product.Create": "enums:auditAction.Product.Create",
"Product.Update": "enums:auditAction.Product.Update",
"Product.Activate": "enums:auditAction.Product.Activate",
"Product.Deactivate": "enums:auditAction.Product.Deactivate",
"EggUnitConversion.Update": "enums:auditAction.EggUnitConversion.Update",
} as const satisfies Record<AuditActionValue, EnumsKey>;
export function auditActionLabel(value: AuditActionValue | (string & {})): string {
const key = AUDIT_ACTION_KEYS[value as AuditActionValue];
return key ? i18n.t(key) : String(value);
}

// ---------------------------------------------------------------------------
// entity type (AuditPage table entity cell) — AuditEvent.entityType.
// ---------------------------------------------------------------------------
export const ENTITY_TYPE_VALUES = [
"Account", "DailyEntry", "EggGrade", "EggUnitConversion", "Expense",
"ExpenseCategory", "Flock", "InventoryItem", "Payment", "Product",
"SalesOrder", "User", "WaterUsage",
] as const;
export type EntityTypeValue = (typeof ENTITY_TYPE_VALUES)[number];
const ENTITY_TYPE_KEYS = {
Account: "enums:entityType.Account",
DailyEntry: "enums:entityType.DailyEntry",
EggGrade: "enums:entityType.EggGrade",
EggUnitConversion: "enums:entityType.EggUnitConversion",
Expense: "enums:entityType.Expense",
ExpenseCategory: "enums:entityType.ExpenseCategory",
Flock: "enums:entityType.Flock",
InventoryItem: "enums:entityType.InventoryItem",
Payment: "enums:entityType.Payment",
Product: "enums:entityType.Product",
SalesOrder: "enums:entityType.SalesOrder",
User: "enums:entityType.User",
WaterUsage: "enums:entityType.WaterUsage",
} as const satisfies Record<EntityTypeValue, EnumsKey>;
export function entityTypeLabel(value: EntityTypeValue | (string & {})): string {
const key = ENTITY_TYPE_KEYS[value as EntityTypeValue];
return key ? i18n.t(key) : String(value);
}

// Machine-iterable registry of every family: its raw-value tuple, its key map,
// and its label function. enums.test.ts walks this to assert each value resolves
// to a real, non-empty en.enums entry — so a new family added above is covered
Expand Down Expand Up @@ -303,4 +384,6 @@ export const ENUMS = {
},
unitSystem: { values: UNIT_SYSTEM_VALUES, keys: UNIT_SYSTEM_KEYS, label: unitSystemLabel },
weekday: { values: WEEKDAY_VALUES, keys: WEEKDAY_KEYS, label: weekdayLabel },
auditAction: { values: AUDIT_ACTION_VALUES, keys: AUDIT_ACTION_KEYS, label: auditActionLabel },
entityType: { values: ENTITY_TYPE_VALUES, keys: ENTITY_TYPE_KEYS, label: entityTypeLabel },
} as const;
244 changes: 244 additions & 0 deletions web/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,203 @@ export const es = {
outstandingHeader: "Pendiente",
},

// machine-drafted (#182) — pending native review. Task 28 (B5): new
// namespace, backfilling es so Spanish mode renders translated text on the
// History screen. Keys mirror en.ts history exactly, including the
// {{status}}/{{date}}/{{flock}}/{{total}}/{{mortality}}/{{reason}}/{{time}}
// placeholders. "Daily entry page" (noEntriesMatch) is kept in English,
// matching the existing sales.addCustomerFirst precedent of leaving an
// unexternalized screen's name untranslated until dailyEntry itself is
// added to TRANSLATED_NAMESPACES.
history: {
loadingTitle: "Historial",
title: "Historial de entradas diarias",

intro:
"Las entradas presentadas y bloqueadas se pueden ajustar o anular "
+ "aquí — las existencias y el registro de aves se actualizan "
+ "automáticamente; los huevos ya vendidos nunca se modifican. "
+ "Siempre se requiere un motivo.",

concurrentConflictMessage:
"Esta entrada acaba de cambiar en otro lugar — la lista se ha vuelto a cargar; vuelva a intentarlo.",
loadFlocksGradesFailed: "No se pudieron cargar los lotes/grados.",
loadEntriesFailed: "No se pudieron cargar las entradas.",
conflictRebindMessage:
"Esta entrada fue cambiada por otra persona — el formulario muestra "
+ "los valores más recientes; vuelva a aplicar su corrección.",
nothingToAdjustMessage: "Esta entrada ahora está {{status}} — no queda nada por ajustar.",
conflictReloadFailedMessage:
"Esta entrada fue cambiada por otra persona y la lista no se pudo "
+ "volver a cargar — vuelva a cargar la página antes de reintentarlo.",
exceedsSellableMessage:
"Las cantidades clasificadas no pueden superar el total de huevos menos los agrietados/sucios/descartados.",
entryAdjustedMessage: "Entrada ajustada — las existencias y el registro de aves se actualizaron para coincidir.",
adjustReloadFailedMessage: "El ajuste se guardó, pero la lista no se pudo volver a cargar — actualice la página.",
voidConfirmTitle: "¿Anular la entrada del {{date}} para {{flock}}?",
voidConfirmBody:
"Sus lotes de huevos quedan vacíos y sus muertes se revierten. La "
+ "entrada se conserva como Anulada. Se rechaza si alguno de sus "
+ "huevos ya fue vendido.",
voidConfirmLabel: "Anular entrada",
entryVoidedMessage: "Entrada anulada — sus lotes de huevos quedaron vacíos y sus muertes se revirtieron.",
voidReloadFailedMessage: "La anulación se guardó, pero la lista no se pudo volver a cargar — actualice la página.",
voidConflictMessage:
"Esta entrada fue cambiada por otra persona — la lista se ha vuelto a cargar; vuelva a intentarlo.",
voidConflictReloadFailedMessage:
"Esta entrada fue cambiada por otra persona y la lista no se pudo "
+ "volver a cargar — vuelva a cargar la página.",
loadMoreFailedMessage: "No se pudo cargar más.",

// Filters
flockLabel: "Lote",
allFlocksOption: "Todos los lotes",
fromLabel: "Desde",
toLabel: "Hasta",

// Adjust dialog
adjustDialogTitle: "Ajustar entrada",
adjustDialogTitleWithEntry: "Ajustar — {{date}}, {{flock}}",
previouslyAdjusted:
"Ajustado anteriormente (total {{total}}, mortalidad {{mortality}} — \"{{reason}}\").",
totalEggsLabel: "Total de huevos",
crackedLabel: "Agrietados",
dirtyLabel: "Sucios",
discardedLabel: "Descartados",
deathsLabel: "Muertes",
inactiveGradeSuffix: " (inactivo)",
reasonLabel: "Motivo *",
saveAdjustmentButton: "Guardar ajuste",

noEntriesMatch: "Ninguna entrada coincide — registre una en la página Daily entry.",

// Entries table
dateHeader: "Fecha",
flockHeader: "Lote",
statusHeader: "Estado",
totalHeader: "Total",
// NOTE (flag for native review): "cr/di/ds" abbreviates the English
// cracked/dirty/discarded; re-abbreviated here to the initials of the
// Spanish words (ag/su/de) — confirm this reads clearly as a
// table-header abbreviation.
lossesHeader: "Pérdidas (ag/su/de)",
mortalityHeader: "Mortalidad",
gradedHeader: "Clasificado",
editButton: "editar",
adjustButton: "ajustar",
voidButton: "anular",
loadMoreButton: "cargar más",

// Entry-status pills
statusVoided: "Anulado",
statusAdjusted: "Ajustado",
statusLocked: "Bloqueado",
lockedAt: "Bloqueado {{time}}",
statusSubmitted: "Presentado",
statusDraft: "Borrador",
},

// machine-drafted (#182) — pending native review. Task 28 (B5): new
// namespace, backfilling es so Spanish mode renders translated text on the
// Reports screen. Keys mirror en.ts reports exactly, including the
// {{count}}/{{revenue}}/{{paid}}/{{outstanding}}/{{total}}/{{expenses}}/
// {{profit}} placeholders and the <strong> tag in profitLine.
reports: {
title: "Informes",
fromLabel: "Desde",
toLabel: "Hasta",

productionHeading: "Producción",
dateHeader: "Fecha",
eggsHeader: "Huevos",
lossesHeader: "Pérdidas (ag/su/de)",
sellableHeader: "Vendible",
deathsHeader: "Muertes",
henDaysHeader: "Días-gallina",
henDayPctHeader: "% días-gallina",
periodRowLabel: "Período",
gradeTotalsLabel: "Por grado:",

moneyHeading: "Dinero",
salesRowLabel: "Ventas",
salesSummary:
"{{count}} pedido(s) confirmado(s) — ingresos {{revenue}}, pagado {{paid}}, pendiente {{outstanding}}",
salesVoidedSuffix: " ({{count}} anulado(s))",
expensesRowLabel: "Gastos",
expensesNone: "sin registrar",
expensesTotalSuffix: " — total {{total}}",
profitRowLabel: "Ganancia (básica)",
profitLine: "ingresos {{revenue}} − gastos {{expenses}} = <strong>{{profit}}</strong>",
profitFootnote:
"La ganancia \"básica\" es el ingreso confirmado menos los gastos "
+ "registrados — sin costo de bienes vendidos ni valoración de inventario.",
},

// machine-drafted (#182) — pending native review. Task 29 (B5): new
// namespace, backfilling es so Spanish mode renders translated text on the
// Audit screen. Keys mirror en.ts audit exactly (no placeholders in this
// namespace — the action/entity table cells route through the already-
// translated enums:auditAction.*/entityType.* labels, not this namespace).
audit: {
heading: "Registro de auditoría",
intro:
"Todo cambio correctivo, destructivo o de configuración — quién lo "
+ "hizo, cuándo y por qué. Las filas se escriben junto con el cambio "
+ "mismo y nunca se editan.",
actionFilterLabel: "Acción",
allActionsOption: "Todas las acciones",
whenHeader: "Cuándo (UTC)",
whoHeader: "Quién",
actionHeader: "Acción",
entityHeader: "Entidad",
reasonHeader: "Motivo",
emptyMessage: "Aún no hay eventos de auditoría.",
loadMoreButton: "cargar más",
},

// machine-drafted (#182) — pending native review. Task 30 (B5): new
// namespace, backfilling es so Spanish mode renders translated text on the
// Export screen. Keys mirror en.ts export exactly (no placeholders in this
// namespace). The "dataset.<slug>" keys are DISPLAY labels for the dataset
// picker (the wire value stays the raw slug) — translated to natural
// Spanish per the task brief.
export: {
heading: "Exportar",
intro:
"Descargue los datos de su cuenta como archivos CSV — una copia de "
+ "seguridad manual que puede guardar donde quiera. Los valores "
+ "monetarios se exportan en unidades menores (centavos) con su "
+ "moneda, exactamente como se almacenan.",

fullBackupHeading: "Copia de seguridad completa",
fullBackupButton: "Descargar copia de seguridad completa (zip)",
fullBackupHint: "Un zip con todos los conjuntos de datos a continuación más un manifiesto de recuentos de filas.",
preparingButton: "Preparando…",

singleDatasetsHeading: "Conjuntos de datos individuales",

"dataset.flocks": "Parvadas",
"dataset.bird-movements": "Movimientos de aves",
"dataset.daily-entries": "Entradas diarias",
"dataset.daily-entry-grades": "Grados de entradas diarias",
"dataset.egg-grades": "Grados de huevo",
"dataset.egg-lots": "Lotes de huevo",
"dataset.customers": "Clientes",
"dataset.sales-orders": "Pedidos de venta",
"dataset.sales-order-items": "Artículos de pedidos de venta",
"dataset.sales-order-allocations": "Asignaciones de pedidos de venta",
"dataset.payments": "Pagos",
"dataset.inventory-items": "Artículos de inventario",
"dataset.inventory-lots": "Lotes de inventario",
"dataset.inventory-movements": "Movimientos de inventario",
"dataset.feed-usages": "Usos de alimento",
"dataset.water-usages": "Usos de agua",
"dataset.expense-categories": "Categorías de gastos",
"dataset.expenses": "Gastos",
"dataset.egg-inventory-movements": "Movimientos de inventario de huevo",
"dataset.audit-events": "Eventos de auditoría",
},

// machine-drafted (#182) — pending native review. Task 25c (B4): new
// namespace, backfilling es for the closed-vocabulary enum labels
// (status/role/waterSource/waterUnit/gradeType/inventoryCategory/
Expand Down Expand Up @@ -527,5 +724,52 @@ export const es = {
"weekday.Thursday": "Jueves",
"weekday.Friday": "Viernes",
"weekday.Saturday": "Sábado",

// machine-drafted (#182) — pending native review. Task 29 (B5): audit
// action + entity type labels for AuditPage. Keys mirror en.ts exactly
// (flat "auditAction.Entity.Verb" / "entityType.Value" strings,
// keySeparator:false — see en.ts's auditAction header comment).
"auditAction.DailyEntry.Adjust": "Entrada diaria ajustada",
"auditAction.DailyEntry.Void": "Entrada diaria anulada",
"auditAction.SalesOrder.Void": "Pedido de venta anulado",
"auditAction.Payment.Void": "Pago anulado",
"auditAction.Expense.Adjust": "Gasto ajustado",
"auditAction.ExpenseCategory.Update": "Categoría de gasto actualizada",
"auditAction.InventoryItem.Adjust": "Artículo de inventario ajustado",
"auditAction.WaterUsage.Correct": "Uso de agua corregido",
"auditAction.Flock.BirdMovement": "Movimiento de aves registrado",
"auditAction.Flock.Update": "Lote actualizado",
"auditAction.Flock.Deplete": "Lote agotado",
"auditAction.Flock.Archive": "Lote archivado",
"auditAction.Flock.Reactivate": "Lote reactivado",
"auditAction.EggGrade.Update": "Grado de huevo actualizado",
"auditAction.EggGrade.Activate": "Grado de huevo activado",
"auditAction.EggGrade.Deactivate": "Grado de huevo desactivado",
"auditAction.User.Create": "Usuario creado",
"auditAction.User.Update": "Usuario actualizado",
"auditAction.User.PasswordSet": "Contraseña establecida",
"auditAction.User.PasswordChanged": "Contraseña cambiada",
"auditAction.User.FlockAssign": "Lote asignado al usuario",
"auditAction.User.FlockUnassign": "Lote desasignado del usuario",
"auditAction.Account.Export": "Datos exportados",
"auditAction.Product.Create": "Producto creado",
"auditAction.Product.Update": "Producto actualizado",
"auditAction.Product.Activate": "Producto activado",
"auditAction.Product.Deactivate": "Producto desactivado",
"auditAction.EggUnitConversion.Update": "Conversión de unidad de huevo actualizada",

"entityType.Account": "Cuenta",
"entityType.DailyEntry": "Entrada diaria",
"entityType.EggGrade": "Grado de huevo",
"entityType.EggUnitConversion": "Conversión de unidad de huevo",
"entityType.Expense": "Gasto",
"entityType.ExpenseCategory": "Categoría de gasto",
"entityType.Flock": "Lote",
"entityType.InventoryItem": "Artículo de inventario",
"entityType.Payment": "Pago",
"entityType.Product": "Producto",
"entityType.SalesOrder": "Pedido de venta",
"entityType.User": "Usuario",
"entityType.WaterUsage": "Uso de agua",
},
} as const;
Loading