Model labels (ajustare, bonusuri/avansuri) drive the plural title
'Bonusuri/avansuri' and 'Create Ajustare' text; TYPES enum
(Bonus/Penalizare/Avans) drives the type select; column 'Aplicat'
and auto-generated 'Period' for the period field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Notification 'Calculat salariul YYYY-MM pentru N utilizatori' was
built via string interpolation → couldn't translate. Now uses __()
with :period / :count placeholders. Adds 7 more translations for
section titles and column labels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Section titles (Profil, Salariu & comisioane), commission column
headers (% Manopere / % marja piese), plural model label, hire date
label, and both commission helper texts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Model labels used lowercase (Expense::getModelLabel returns __('cheltuială'))
but dict only had capitalised keys. Filament rendered 'Создать
Cheltuială' as fallback. Now RU shows 'Создать расход' / EN 'Create
expense'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- All 4 tabs (Overview/Cashflow/P&L/Balanță) built with __() around
the text portion, emoji stays in code
- 4 period presets wrapped with __()
- P&L table Marjă/Marjă piese footer wrapped
- Expense::CATEGORIES values look up through __() in the expensesByCat
loop so categories show translated (Salariu → Зарплата etc.)
- +25 translations covering all UI copy on /app/finance
306 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two CheckboxList options passed raw Vehicle::BODY_TYPES and
Vehicle::TRANSMISSION_TYPES arrays — now they run through I18n::opts()
so the RO values translate at display. +6 translations for the enum
values that weren't yet in dict (Crossover, Pickup, Minivan, DSG,
DCT, AMT).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrapped the dynamic Select 'key' label (Brand/Categorie) with __(),
switched the category options to I18n::opts(). Notification 'Recalculat
preț pentru N piese' now uses __(:n) placeholder.
+11 keys: regulă, reguli markup, Cheie, Mai mic = aplicat primul.,
'Va recalcula sell_price…' confirm text, notification, 'Interval preț'
enum, and missing Part.CATEGORIES (Ulei, Filtre, Lichide, Distribuție).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Migration: create `units` table + nullable unit_id FK on
parts / wo_parts / purchase_items / labor_parts. Seeds ~11 standard
units (buc/set/l/ml/kg/g/m/cm/m²/oră/pack) for every existing
tenant and backfills unit_id by matching the legacy string `unit`
column. Keeps `unit` string as fallback so old code paths keep
rendering.
- Unit model: label(locale), forSelect(locale), labelFor(id, code)
helpers. All 4 owner models get unitModel() relation + unitLabel()
accessor.
- UnitResource under Depozit group with Filament UI: code, sort,
is_active + separate name_ro/name_ru/name_en fields.
- Filament forms/tables updated: Part / PurchaseItem / LaborPart /
WorkOrderPart now use Select('unit_id')->options(Unit::forSelect())
for input and TextColumn->getStateUsing(unitLabel()) for display.
Selecting a part auto-fills unit_id when the source has one.
- +10 translations for the new resource + defaults; nav.label
'Unități de măsură' added to all 3 lang files.
All 306 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Supplier form: Categorii, Zile livrare, Net 30 / Avans placeholder,
Rerating action, Date insuficiente + helper.
Purchase form auto-generated headlines: Contact name, Pay terms,
Delivery days, Categories, Order date, Expected at, Received at.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrapped 3 remaining raw RO strings ('An model (estimat)', WMI error
fallback, 'Niciun rezultat pentru') and added human RU/EN for all
15 keys used on /app/vin-search.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap inline blade text ('Mod recepție'), placeholder, and JS error
messages (via @json(__(...)) so they land as JS strings).
- +13 translations covering all UI text on /app/scanner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'piesă' (lowercase, Part::getModelLabel()) was identity-fallback so
Filament rendered 'Редактирование Piesă' instead of 'Редактирование
запчасть'. Also 'piese' was translated to genitive ('запчастей') from
an earlier pass where it was used as a count suffix — reverted to
nominative since it's the model label proper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filament's default empty state used the model class name ('service
template items') and default 'Create service template item' modal
heading. Both are now explicit __() calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 'Nou tehnician' button label is auto-generated by Filament as
'New ' + getModelLabel() = 'New ' + __('tehnician'). It picks up the
translation already in dict — on EN it shows 'New technician',
on RU 'Новый техник'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The RelationManager Create modal was showing 'Create Labor Part' from
the model class name. Override CreateAction/EditAction with explicit
->label() + ->modalHeading() so RU/EN see translated titles.
'buc' unit default is stored in DB (raw string); added mapping so it
displays 'шт' on RU / 'pcs' on EN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous sed pass only matched \$state; missed \$s and other arg-name
variants. This time the regex is arg-name-agnostic and touches 21
files across Filament resources & relation managers.
Also wraps two special cases: UserResource role-labels lookup and
LaborResource pricing_mode ternary ('Fix' | 'Pe oră').
+27 human translations for the enum values that were still identity
fallback: WorkOrderWork.STATUSES (De făcut), Purchase.STATUSES,
OnlineOrder.STATUSES, Call.DIRECTIONS/STATUSES, BodyshopJob.TYPES/
STATUSES, TireSet.SEASONS, MessageTemplate.CHANNELS,
DamagePoint.SEVERITIES, ServiceTemplateItem.KINDS.
All 306 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- getContentTabLabel: 'ⓘ Info & antet' now goes through __()
- Tracking-client modal heading translated ('Tracking client — WO #…')
- Both 'Închide' cancel labels wrapped in __()
- Tracking modal widened to 'lg' so the QR + link input fit without overlap
- Notification 'Șablon aplicat' template built with __() so RU/EN see
translated pieces
- +16 translations covering the QR modal, Info tab, Închide etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+ Pod nou, Programare nouă (title), Subiect *, Data *, Schimb ulei + filtru
(placeholder), Ex: Curte 1, Pod 4, Atelier electric (placeholder).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds RU/EN for the strings still showing raw RO on /app/clients/*/edit:
Date generale, Persoană fizică, Persoană juridică, Contacte, Telefon
alternativ, Se completează automat…, Whatsapp, Viber, Telegram, Canal
marketing, Financiar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Post/mechanic meta 'h/zi' → __('h/zi') so RU shows 'ч/день', EN 'h/day'
- Wrap the 3 load-legend labels (0–5h/10, 5–8.5h/10, ≥9h/10) so 'h' → 'ч' on RU
- Split the long howto sentence so 'Pod'/'Mecanic' + 'Zile' translate
- Default fallback 'Pod 1 (default)' now uses __('Pod') + __('implicit')
Adds 5 new translations. Post NAMES themselves are DB rows (user data)
and are shown as-is — they're not translated by the app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batch pass covering the labels the user reported (Tarifare, Preț fix,
Piese impl., Specializare, Tarif/h) plus 270+ other short RO strings
that were still identity-fallback: Filament section titles, form-field
labels, table columns with explicit ->label(__()), buttons, notifications,
placeholders, helper texts, empty-state messages.
Down from ~514 to ~253 remaining identity-fallback RO strings — the rest
are mostly long help-text paragraphs which are lower priority.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of untranslated table headers: Filament's HasLabel::getLabel()
auto-generates a label from the field name (e.g. `name` → "Name") but only
calls __() on it if you also invoke ->translateLabel(). Without that, the
auto-generated label always renders in English regardless of app locale.
Fix: configureUsing() in AppServiceProvider makes translateLabel() the
default for every Column, BaseFilter, Field, Action and BulkAction.
Then added 175 human RU/EN translations for Filament's auto-generated
keys (Name, Phone, Email, Status, Balance, Created at, Plate, Make,
Model, Year, VIN, Mileage, Owner, Client, Vehicle, Amount, Total, Qty,
Actions, Search, Filter, Export, Import, plus enums/relationships).
All 306 tests pass. Deploy will fix table headers on
Clients / Vehicles / Work orders / Payments / Purchases / Suppliers etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Broad sweep across the whole codebase:
- Blade views (39 files, 315 wraps): tag-text and title/placeholder/alt
attributes wrapped with {{ __() }}. Excludes scripts, styles, @php,
@verbatim, {{ }}, {!! !!}, comments to avoid touching interpolations.
- PHP (54 files, 179 wraps): array 'key' => 'RO value' patterns and
list items with diacritics wrapped with __(). Reverted __() inside
const arrays (PHP disallows non-constant expressions).
- Added 269 new keys to lang/{ru,en}.json (identity fallback for
unknowns → 161 human RU + 163 EN translations added for the most
common enums, stages, roles, statuses, payment methods, vehicle
categories, warehouse, portal, form actions.
Missing translations fall back to RO so the UI never breaks. All 306
tests pass; view cache compiles cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Convert static $modelLabel/$pluralModelLabel/$title to getter methods
- Wrap ->label()/->placeholder()/->helperText()/->description()/->title()/->body() args
- Wrap Section::make()/Fieldset::make()/Notification::make()->title() args
- Fix RelationManagers::getTitle() signature to match parent (Model, string)
- Fix Pages::getTitle() to instance method (BasePage::getTitle is non-static)
- Extend lang/ru.json + lang/en.json with 700+ common terms; identity fallback for the rest
- Remove duplicate getters in 5 resources that had manual getModelLabel already
All 306 tests pass. Missing translations fall back to the RO key so the UI never breaks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User screenshots showed the tenant admin panel (Filament) had sidebar
labels stuck in Romanian even when switching to Russian: 'Cereri',
'Calendar vizual', 'Atelierul meu', 'KPI mecanici', 'Fișe lucru',
'Norme-ore', 'Tehnicieni', 'Șabloane servicii', 'Depozite', 'Scaner',
'Depozit', 'VIN-căutare', 'Furnizori', 'Achiziții', 'Procentaj',
'Coeficienți preț', plus all group headers.
Root cause: every Filament Resource and Page had static properties
'protected static ?string $navigationLabel = "Fișe lucru"' — string
literals baked into class definitions. Static properties don't run
through the translation layer.
Fix in two parts:
1. New translation files with 52 label keys + 12 group keys:
- lang/ro/nav.php — Romanian (identity)
- lang/ru/nav.php — full Russian translations (Заказ-наряды,
Автомобили, Клиенты, Календарь, Моя мастерская, Механики KPI,
Настройки, etc.)
- lang/en/nav.php — English translations (Work orders, Vehicles,
Clients, Calendar, My workshop, Mechanic KPI, Settings, etc.)
Keyed by the Romanian original so lookups map 1:1 —
'nav.label.Fișe lucru' returns 'Заказ-наряды' in RU, 'Work orders'
in EN, 'Fișe lucru' in RO.
2. Python transformer converted 54 files:
- 33 Filament Tenant Resources
- 15 Filament Tenant Pages
- 4 Filament Central Resources
- 1 Filament Central Page
- 1 Widget
Each 'protected static ?string $navigationLabel = "X";' became
'public static function getNavigationLabel(): string { return
__("nav.label.X"); }'. Same treatment for $navigationGroup.
Cleanup: 6 resources already had manually-added getNavigationLabel
methods from an earlier partial effort — those used flat JSON keys
(__("Cereri")) that never resolved. Deduped so only the nav.label.*
version remains.
Untouched (intentional):
- $modelLabel / $pluralModelLabel (used in breadcrumbs and headings —
still hardcoded, next tier of work)
- Section titles, column headers, form field labels (medium priority)
- $navigationSort (numeric, no translation needed)
- $navigationIcon (icon reference)
Suite: 306 passed (853 assertions). Unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes:
== 1. Language switcher (RO/RU/EN) in portal headers ==
New partial resources/views/partials/lang-switcher.blade.php with two
visual styles:
- style='chip' (default) — white-tinted glass for colored headers
(shop nav bar, tracking page hero)
- style='light' — outlined buttons for pale backgrounds (invitation
accept card)
Each button is a POST form to the existing /locale/{lang} route which:
- puts locale in session
- persists to user.locale if authenticated
- redirects back to the same page
Included in:
- resources/views/shop/layout.blade.php (right side of top nav,
after login/register)
- resources/views/tracking/show.blade.php (top-right of hero header,
absolutely positioned)
- resources/views/invitations/accept.blade.php (top-right of card,
above welcome heading, light style)
Current locale button is highlighted (opaque white on colored bg /
blue on pale bg). Others are muted until hovered.
Two new translation keys:
- portal.common.language (RO: Limbă / RU: Язык)
== 2. apply_margin toggle back in "Plată & total" ==
Previous session's edit didn't persist to the file. Now confirmed in
place: WorkOrderResource form's "Plată & total" section shows a
"Aplică marjă internă" toggle between discount_pct and
override_margin_pct. Gated by FINANCE_VIEW_INTERNAL_MARGIN so only
owner / admin / manager / accountant see it. Default = true.
When toggled off on a Fișă, WorkOrderWork::saving hook writes
salary_base = total for every line on that WO (no reduction).
Backend logic already in place — this commit fixes the missing UI
control.
Suite: 303 passed (840 assertions). Unchanged — refactor + view only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes in one commit:
== 1. Moved apply_margin toggle from line-level to Fișă-level ==
The per-manoperă apply_margin toggle is gone from the Manopere tab.
In its place: a single "Aplică marjă internă" toggle in Fișa's
"Plată & total" section (next to override_margin_pct). One decision
per Fișă instead of per line — cleaner mental model, matches how the
shop actually thinks about at-cost vs. billable work.
Migration: work_orders.apply_margin boolean default true (idempotent).
WorkOrderWork::saving now reads WO.apply_margin from DB directly (not
via belongsTo cache) to determine salary_base:
- WO.apply_margin=false → every line gets salary_base=total, applied_margin_pct=0
- WO.apply_margin=true → resolver chain (WO.override → user margin → tenant default)
Old wo_works.apply_margin column stays untouched (backward-compat with
existing rows), but no longer exposed in UI. Tests updated to new
semantic. All existing tests green.
== 2. Full i18n audit on client-facing portal — RO/RU separated ==
Problem: user selecting Russian saw Romanian mixed into headings,
buttons, labels. Every client-facing Blade file was 100% hardcoded
Romanian — zero __() calls.
Fix: created lang/ro/portal.php + lang/ru/portal.php with 131 keys
across 3 namespaces:
- portal.common (email, phone, save, total, powered_by, ...)
- portal.invitation (welcome_name, activate_account, expired_body, ...)
- portal.tracking (title_fisa, approve, approval_needed_title,
ready_estimated, hours, unit_pcs, ...)
- portal.shop (catalog, cart, checkout_title, order_number, vin_title,
signin_title, add_to_cart, in_stock, ...)
Converted 15 Blade files to __() calls:
- resources/views/invitations/{accept,expired,invalid}.blade.php
- resources/views/tracking/show.blade.php
- resources/views/shop/{layout,catalog,cart,checkout,order,account,part,vin}.blade.php
- resources/views/shop/auth/{login,register,forgot,reset}.blade.php
Each view's <html lang="{{ app()->getLocale() }}"> now reflects the
resolved locale (was hardcoded lang="ro").
SetLocale middleware resolves locale in this order:
1. session locale (user picked via language switcher)
2. authenticated user.locale
3. tenant.settings.language
4. app.locale default (now 'ro')
Config change: config/app.php default locale + fallback both = 'ro'
(was 'en'). English falls back to Romanian for portal.* keys since
we don't ship English portal translations — a Romanian shop that
switches to English shows Romanian text, which is safer than showing
"portal.invitation.activate_account" literals.
phpunit.xml sets APP_LOCALE=ro so test assertSee() calls that look
for Romanian text pass.
Verified via portal.* grep: 131 __() calls across 15 files. Zero
hardcoded Romanian nouns/verbs left in any client-facing view.
Suite: 303 passed (840 assertions). Unchanged count — refactor,
not new tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>