feat(pos): add multi-language i18n (#37) and multi-currency USD/MXN (#38)

- i18n.js with 130+ translation keys for es/en, loaded in all 11 templates
- sidebar.js uses t() for all nav labels, adds MX/US language toggle
- app-init.js role labels use i18n
- currency.py service with convert() and format_currency()
- config.py adds DEFAULT_CURRENCY and EXCHANGE_RATE_USD_MXN settings
- config_bp.py adds GET/PUT /pos/api/config/currency endpoints
- config.html adds currency/exchange-rate section (Section 8)
- config.js adds loadCurrency/saveCurrency with localStorage sync
- pos.js fmt() reads pos_currency from localStorage for USD/MXN display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 08:19:18 +00:00
parent e00dce7d5a
commit c1d0638b45
15 changed files with 554 additions and 24 deletions

View File

@@ -2301,6 +2301,7 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/accounting.js"></script>

View File

@@ -737,6 +737,7 @@
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">&times;</button>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/catalog.js"></script>

View File

@@ -1702,6 +1702,44 @@
</div>
</div>
<!-- ===============================================================
SECTION 8: MONEDA / CURRENCY
=============================================================== -->
<div class="settings-section">
<div class="settings-section__header">
<div class="settings-section__icon">
<svg viewBox="0 0 24 24"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
</div>
<div>
<div class="settings-section__title" data-i18n="currency_config">Moneda</div>
<div class="settings-section__desc">Configura la moneda y tipo de cambio para ventas</div>
</div>
</div>
<div class="settings-card" style="padding:var(--space-5);">
<div class="form-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);">
<div class="form-group">
<label class="form-label" data-i18n="default_currency">Moneda Predeterminada</label>
<select class="form-input" id="cfg-currency" style="padding:var(--space-2) var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius-sm,4px);background:var(--color-bg-base);color:var(--color-text-primary);font-size:var(--text-body-sm);">
<option value="MXN">$ MXN — Peso Mexicano</option>
<option value="USD">US$ USD — US Dollar</option>
</select>
</div>
<div class="form-group">
<label class="form-label" data-i18n="exchange_rate">Tipo de Cambio USD/MXN</label>
<input class="form-input" id="cfg-exchange-rate" type="number" step="0.01" min="0.01" value="17.50"
style="padding:var(--space-2) var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius-sm,4px);background:var(--color-bg-base);color:var(--color-text-primary);font-size:var(--text-body-sm);font-family:var(--font-mono);" />
</div>
</div>
<div style="margin-top:var(--space-4);display:flex;gap:var(--space-3);align-items:center;">
<button class="btn btn--primary" id="btn-save-currency" style="padding:var(--space-2) var(--space-4);font-size:var(--text-body-sm);" onclick="Config.saveCurrency()">
Guardar Moneda
</button>
<span id="currency-status" style="font-size:var(--text-caption);color:var(--color-text-muted);"></span>
</div>
</div>
</div>
</div><!-- /content-scroll -->
</main>
</div><!-- /app-shell -->
@@ -1876,6 +1914,7 @@
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
</style>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/config.js"></script>

View File

@@ -2147,6 +2147,7 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/customers.js"></script>

View File

@@ -962,6 +962,7 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/fleet.js"></script>

View File

@@ -2525,6 +2525,7 @@
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">&times;</button>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/inventory.js"></script>

View File

@@ -2723,6 +2723,7 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/invoicing.js"></script>

View File

@@ -1846,6 +1846,7 @@
</div>
<!-- End app-shell -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/reports.js"></script>

View File

@@ -531,6 +531,7 @@ function posLogout(){localStorage.removeItem('pos_token');window.location.href='
</script>
<!-- Sidebar -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/whatsapp.js"></script>
<script src="/pos/static/js/sidebar.js"></script>