mirror of
https://github.com/we-promise/sure.git
synced 2026-05-27 22:44:55 +00:00
* feat(i18n): complete Catalan translations + extract residual hardcoded strings
CA coverage
- All view/model/breadcrumb/doorkeeper/mailer locale files for ca: 0 missing
keys (was ~3,400). Translations follow informal "tu" register, sentence case,
domain glossary (Compte/Saldo/Transacció/Posició/Operació/Pressupost/...).
- Catalan pluralization test: ca uses one/other; mirrors
test/lib/polish_pluralization_test.rb.
- 8 LanguageTool-flagged grammar fixes applied (Connexió òrfena, Secret de
l'API, comma-pero, apostrophe elisions, etc).
Hardcoded string extraction (also fixes EN parity)
- UI::Account::Chart#title + chart.html.erb view tabs -> UI.account.chart.*
- UI::Account::BalanceReconciliation labels + tooltips ->
UI.account.balance_reconciliation.{labels,tooltips}.*
- transactions/_transfer_match.html.erb (Auto-matched, A/M, Confirm/Reject
match, Payment/Transfer is confirmed) -> transactions.transfer_match.*
- AccountOrder labels (Name/Balance asc/desc) -> account_order.* keys with
fallback to existing hardcoded labels.
- Depository::SUBTYPES surface in account list -> depositories.subtypes.*.*
- User role badge -> users.roles.* (admin / member / super_admin).
- 110+ country names -> countries.* (config/locales/countries.ca.yml).
Breadcrumb locale fix
- Breadcrumbable was a before_action that ran before Localize's around_action
switched I18n.locale, so default crumbs rendered in EN even when locale=ca.
- Convert to helper_method that defers translation to render-time (when
I18n.locale is already correct). Add all missing breadcrumb keys to ca + en.
- Layouts switched from @breadcrumbs to breadcrumbs helper.
Locale-aware helpers / formatters
- ApplicationHelper#localized_ordinal: ordinalize that respects ca
(1r/2n/3r/4t/Nè). Wired into preferences month_start_day select.
- Family#moniker_label / moniker_label_plural: translate the default "Family"/
"Group" monikers via shared.family_moniker.* with fallback to the family's
custom override.
- Budget#name: use I18n.l for month_year/short/long instead of strftime("%B %Y")
so the budget header date follows the active locale.
Tooling
- script/lt_check_ca.rb: batched LanguageTool checker (premium endpoint when
LT_USERNAME/LT_API_KEY are set, free fallback otherwise), picky mode,
motherTongue=en for false-friend detection.
- lib/tasks/i18n_screenshot.rake: dev-only rake to set user.locale=ca and
role=super_admin on the demo user so the i18n surfaces can be walked.
Out of scope (pre-existing, not introduced here)
- Native browser file input "Choose Files / No file chosen" (browser locale).
- D3.js client-side chart x-axis dates (JS-side Intl.DateTimeFormat needed).
- Sankey/donut labels = seed category names (data, not i18n).
- 2 rails-i18n datetime/errors interpolation warnings inherited from
config/locales/defaults/ca.yml.
* fix(i18n): apply idiomatic Catalan review (3-agent + native review)
Three parallel review agents flagged 203 findings (31 high / 73 medium / 99 low)
across all 111 ca.yml files. This commit applies the high-severity bugs plus a
curated subset of medium-impact fixes.
Grammar / agreement
- provider_sync_summary.health.stale_pending: `(exclòs)` -> `(exclosa/excloses)`
to agree with feminine `transacció(s)`.
- accounts.confirm_unlink.warning_no_sync: added reflexive `es` -
`el compte ja no es sincronitzarà`.
- sophtron_setup_required.heading: `no configurats` -> `sense configurar`
(avoids broken agreement across "ID" masc. + "clau" fem.).
- admin.sso_providers.form.errors_title: split into one/other pluralization
keys (en + ca); singular `ha impedit` was wrong for count > 1.
Brand consistency
- IndexaCapital -> Indexa Capital (37 occurrences across one file).
- Lunchflow -> Lunch Flow in two remaining places.
Anglicisms / domain mistranslations
- kraken_items setup_accounts.instructions: `ompliments d'operacions`
(lit. dental/food fillings) -> `execucions d'operacions`.
- settings kraken_panel.read_only_title: `Sincronització d'intercanvi`
(swap/trade) -> `Sincronització només de lectura amb l'exchange`.
- transactions convert_to_trade.security_custom + security_not_listed_hint:
`cotització` (price quote) -> `ticker` (the EN field IS a ticker symbol).
- loans.form.rate_type: `Tipus d'interès` collided with sibling
interest_rate -> `Modalitat del tipus`.
- brex_items.provider_panel.sandbox_note_html: `L'staging` (broken
contraction) -> `el staging`.
Idiom traps
- coinbase/binance/kraken wait_for_sync: `acabi de sincronitzar` is
ambiguous in CA (`acabar de + inf` reads as "has just done X") ->
`acabi la sincronització`.
- chats.ai_greeting.there: `a tothom` -> `''` (the EN fallback "Hey there"
is singular; literal CA `tothom` is plural and wrong for 1:1 chat).
- transactions.split_parent_row.split_label: `Divideix` (imperative) is
wrong as a status badge -> `Divisió` (noun).
- transactions.keep_both (2 occurrences): infinitive `mantenir ambdues` ->
imperative `mantén-les totes dues` to match the sibling Yes/No buttons.
- rules.clear_ai_cache: `Reinicia` (restart) -> `Buida` (empty/clear),
which matches the success notice (`s'està netejant`).
Moniker gender breakage (cross-file)
%{moniker} is interpolated downcased from family.moniker_label and may
resolve to feminine `família`/`llar` or masculine `grup`. Strings that
hard-code a gendered article ('al teu %{moniker}', 'aquesta %{moniker}',
'aquest/a %{moniker}') broke on at least one branch. Restructured the
affected sentences to drop the gendered determiner:
- account_sharings.show.no_members
- merchants.family_empty / family_title / provider_empty
- registrations.new.join_family_title
- settings.preferences.show.currencies_subtitle / sharing_subtitle
- simplefin_items.select_existing_account.no_accounts_found
- invitations.new.subtitle
- invitation_mailer.invite_email.subject (mailers/) + body (views/)
- snaptrade_items.providers.snaptrade.free_tier_warning
Terminology consistency
- models/account_statement/ca.yml attributes aligned with view-side
forms: `Saldo d'obertura`/`Saldo de tancament` ->
`Saldo inicial`/`Saldo final`; `Suggeriment de...` -> `Pista de...`.
- account_statements.coverage.status.not_expected:
`No s'esperava` -> `No previst` (status label, not past action).
- account_statements.index.empty_unmatched: aligned with the section's
own label `Safata sense aparellar`.
- imports.create.document_provider_not_configured + document_upload_failed:
`arxiu vectorial` -> `magatzem vectorial` (correct TermCat term).
- coinstats_items blockchain gender: `els blockchains` / `un blockchain` ->
`les blockchains` / `una blockchain` (feminine per TermCat).
- accounts.account.remove_default: `Treu el predeterminat` ->
`Treu com a predeterminat` (pairs with sibling `Estableix com a
predeterminat`).
- accounts.tax_treatments.tax_deferred: `Diferit fiscalment` (lit. calque)
-> `Tributació diferida` (standard CA tax-accounting term).
- settings.payments.show.currently_on_plan: `Actualment al` ->
`Actualment al pla:` (was a fragment).
Out of scope (review flagged, not applied here)
- LOW-severity stylistic preferences (Veure vs Mostra, etc).
- `models/category/ca.yml` default category names — seeded at family
creation, not via I18n at runtime, so changes wouldn't affect existing
families.
- `models/period/ca.yml` short labels mixing EN (MTD/YTD) and CA (STD/MA)
— needs a one-convention decision separately.
* fix(i18n,ca): drop gendered article in period_activity + tighten cash-flow terms
- pages.dashboard.investment_summary.period_activity: 'Activitat del
%{period}' contracted 'del' = 'de el' (masc.sg.). %{period} resolves
to mixed forms ('Setmana en curs' fem, 'Últims 30 dies' pl., 'Any en
curs' apostrophe), so hard-coded 'del' was wrong on most labels.
Replaced with 'Activitat — %{period}' (em-dash) to skip the
contraction entirely.
- pages.dashboard.outflows_donut.title / total_outflows: switched from
bare 'Sortides' / 'Total de sortides' to 'Sortides de caixa' /
'Total de sortides de caixa' to match TermCat's precise term
('sortida de caixa' = cash outflow).
* fix(i18n,ca): rephrase transfer source/destination amount labels
'Import d'origen' / 'Import de destinació' were literal calques of
'Source amount' / 'Destination amount'. In a multi-currency transfer
form (sender/receiver in different currencies) the natural CA pair is
'Import enviat' / 'Import rebut'.
* fix(i18n,ca): 'Dades en brut' -> 'Dades sense processar'
The literal calque of 'Raw data' read as too technical for personal-
finance UI. 'Dades sense processar' is the more natural Catalan
equivalent for raw/unprocessed data files.
* fix(i18n): localize Import col_sep label + separator options
The CSV upload form rendered 'Col sep' (the auto-humanized attribute
name) plus hardcoded English 'Comma (,)' / 'Semicolon (;)' options
from Import::SEPARATORS.
- activerecord.attributes.import.col_sep added (en + ca: 'Column
separator' / 'Separador de columnes').
- Import.separator_options class method returns translated tuples;
view switched from Import::SEPARATORS to Import.separator_options.
- activerecord.attributes.import.col_seps.{comma,semicolon} added so
the option labels follow the active locale.
* fix(i18n,ca): drop moniker apposition in sharing/currencies section titles
- sharing_title 'Compartició de %{moniker}' rendered as 'Compartició
de Família' (a noun-noun apposition that's odd in CA) -> 'Compartició
de comptes'.
- sharing_subtitle replaced '%{moniker}' with 'entre els membres' so
the sentence reads naturally and doesn't depend on moniker gender.
- currencies_title 'Divises de %{moniker}' had the same apposition
-> 'Divises'. Subtitle no longer references moniker either.
* fix(i18n,ca): keep 'Self Hosting' untranslated
Reverted 'Autoallotjament' / 'autoallotjada' / 'autoallotjats' usages
to the original English 'Self Hosting' (sidebar label, breadcrumbs,
hostings page title, chat assistant settings hint, redis configuration
subheading, LLM usages cost-estimates description).
The brand-style term reads more naturally in EN for technical users
configuring their own deployment.
* fix(i18n,ca): lowercase 'self hosting' (sentence case in labels)
* fix(i18n): extract budget_categories stepper + allocation_progress strings
Hardcoded English strings on the budget category editor:
- 'Setup' / 'Categories' stepper labels in budgets/_budget_nav.html.erb
- 'X% set' / '> 100% set' / 'left to allocate' / 'Budget exceeded by ...'
in budget_categories/_allocation_progress.erb
- '/m avg' caption + 'Shared' placeholder + 'Leave empty to share
parent's budget' tooltip in budget_categories/_budget_category_form
and _uncategorized_budget_category_form
Extracted to:
- budgets.budget_nav.{setup,categories}
- budget_categories.allocation_progress.{percent_set,over_set,left_to_allocate,budget_exceeded_html}
- budget_categories.budget_category_form.{monthly_average,shared_placeholder,shared_title}
CA translations added; EN keys mirror the prior literals.
* chore(i18n): drop translation tooling from PR
These were dev-only helpers used during the Catalan translation pass:
- script/lt_check_ca.rb: LanguageTool API checker (premium/free
endpoint, picky mode, batching). Useful for ongoing locale QA but
shouldn't ship in this feature PR.
- lib/tasks/i18n_screenshot.rake: rake task that flips user.locale and
role on the demo user for walking the i18n surfaces locally.
Both stay available locally; pulled out of the PR scope.
* fix(i18n): apply PR review feedback (CodeRabbit + Codex)
- balance_reconciliation crypto_items: use :end_balance_crypto tooltip
(was :end_balance_investment). Added new UI.account.balance_reconciliation.tooltips.end_balance_crypto key in en + ca.
- doorkeeper.ca.yml confidentiality.no: was YAML boolean false, now string 'No'.
- views/categories: 'Poor contrast, choose darker color or' continued with hardcoded 'auto-adjust.' button text; extracted to categories.form.auto_adjust key (en + ca).
- imports.create.document_upload_failed: 'a l'magatzem' was broken
contraction -> 'al magatzem'.
- invitation_mailer body + mailer subject: 'unir-se' -> 'unir-te' (was
3rd person, should be 2nd to match the rest of the copy).
- 7 strings across mercury_items / sophtron_items / simplefin_items /
lunchflow_items / brex_items / indexa_capital_items / other_assets:
'se sincronitzaran' -> 'es sincronitzaran', 'se segueixen' ->
'es segueixen' (correct reflexive pronoun before consonants).
- settings.providers.status: key was 'false' (YAML-coerced), now 'off'
to match settings/en.yml status.off used in view lookups.
- sophtron_items.sophtron_setup_required.message: stripped trailing
blank line from the quoted scalar.
- settings/profiles/show.html.erb: switched 'family_moniker ==
"Group"' branch checks to 'Current.family&.moniker == "Group"'.
After Family#moniker_label started returning translated values,
callers using the display label for branching would render the
household copy for group families in ca. Compare the stored sentinel
instead.
- Did not apply CodeRabbit's webauthn 'eliminada' -> 'desada' suggestion:
the key is wired to the destroy action (verified at
settings/webauthn_credentials_controller.rb:55), so 'eliminada' is
correct.
232 lines
5.6 KiB
YAML
232 lines
5.6 KiB
YAML
---
|
|
ca:
|
|
activerecord:
|
|
errors:
|
|
messages:
|
|
record_invalid: 'La validació ha fallat: %{errors}'
|
|
restrict_dependent_destroy:
|
|
has_many: No es pot eliminar el registre perquè existeixen %{record} dependents
|
|
has_one: No es pot eliminar el registre perquè existeix un %{record} dependent
|
|
date:
|
|
abbr_day_names:
|
|
- Dg
|
|
- Dl
|
|
- Dm
|
|
- Dc
|
|
- Dj
|
|
- Dv
|
|
- Ds
|
|
abbr_month_names:
|
|
-
|
|
- Gen
|
|
- Feb
|
|
- Mar
|
|
- Abr
|
|
- Mai
|
|
- Jun
|
|
- Jul
|
|
- Ago
|
|
- Set
|
|
- Oct
|
|
- Nov
|
|
- Des
|
|
day_names:
|
|
- Diumenge
|
|
- Dilluns
|
|
- Dimarts
|
|
- Dimecres
|
|
- Dijous
|
|
- Divendres
|
|
- Dissabte
|
|
formats:
|
|
default: "%d-%m-%Y"
|
|
long: "%d de %B de %Y"
|
|
month_year: "%B de %Y"
|
|
short: "%d de %b"
|
|
short_month_year: "%b de %Y"
|
|
month_names:
|
|
-
|
|
- Gener
|
|
- Febrer
|
|
- Març
|
|
- Abril
|
|
- Maig
|
|
- Juny
|
|
- Juliol
|
|
- Agost
|
|
- Setembre
|
|
- Octubre
|
|
- Novembre
|
|
- Desembre
|
|
order:
|
|
- :day
|
|
- :month
|
|
- :year
|
|
datetime:
|
|
distance_in_words:
|
|
about_x_hours:
|
|
one: aproximadament %{count} hora
|
|
other: aproximadament %{count} hores
|
|
about_x_months:
|
|
one: aproximadament %{count} mes
|
|
other: aproximadament %{count} mesos
|
|
about_x_years:
|
|
one: aproximadament %{count} any
|
|
other: aproximadament %{count} anys
|
|
almost_x_years:
|
|
one: quasi %{count} any
|
|
other: quasi %{count} anys
|
|
half_a_minute: mig minut
|
|
less_than_x_minutes:
|
|
one: menys d'%{count} minut
|
|
other: menys de %{count} minuts
|
|
less_than_x_seconds:
|
|
one: menys d'%{count} segon
|
|
other: menys de %{count} segons
|
|
over_x_years:
|
|
one: més d'%{count} any
|
|
other: més d'%{count} anys
|
|
x_days:
|
|
one: "%{count} dia"
|
|
other: "%{count} dies"
|
|
x_minutes:
|
|
one: "%{count} minut"
|
|
other: "%{count} minuts"
|
|
x_months:
|
|
one: "%{count} mes"
|
|
other: "%{count} mesos"
|
|
x_seconds:
|
|
one: "%{count} segon"
|
|
other: "%{count} segons"
|
|
x_years:
|
|
one: "%{count} any"
|
|
other: "%{count} anys"
|
|
prompts:
|
|
day: dia
|
|
hour: hora
|
|
minute: minut
|
|
month: mes
|
|
second: segon
|
|
year: any
|
|
defaults:
|
|
brand_name: "%{brand_name}"
|
|
common:
|
|
close: Tanca
|
|
product_name: "%{product_name}"
|
|
errors:
|
|
format: "%{attribute} %{message}"
|
|
messages:
|
|
accepted: ha de ser acceptat
|
|
blank: no pot estar en blanc
|
|
confirmation: no coincideix
|
|
empty: no pot estar buit
|
|
equal_to: ha de ser igual a %{count}
|
|
even: ha de ser parell
|
|
exclusion: està reservat
|
|
greater_than: ha de ser més gran que %{count}
|
|
greater_than_or_equal_to: ha de ser més gran o igual a %{count}
|
|
in: ha d'estar entre %{count}
|
|
inclusion: no està inclòs a la llista
|
|
invalid: no és vàlid
|
|
less_than: ha de ser menor que %{count}
|
|
less_than_or_equal_to: ha de ser menor o igual a %{count}
|
|
model_invalid: 'La validació ha fallat: %{errors}'
|
|
not_a_number: no és un número
|
|
not_an_integer: ha de ser un enter
|
|
odd: ha de ser senar
|
|
other_than: ha de ser diferent de %{count}
|
|
present: ha d'estar en blanc
|
|
required: ha d'existir
|
|
taken: no està disponible
|
|
too_long:
|
|
one: és massa llarg (%{count} caràcter màxim)
|
|
other: és massa llarg (%{count} caràcters màxim)
|
|
too_short:
|
|
one: és massa curt (%{count} caràcter mínim)
|
|
other: és massa curt (%{count} caràcters mínim)
|
|
wrong_length:
|
|
one: no té la longitud correcta (%{count} caràcter exactament)
|
|
other: no té la longitud correcta (%{count} caràcters exactament)
|
|
template:
|
|
body: 'Hi ha hagut problemes amb els següents camps:'
|
|
header:
|
|
one: No s'ha pogut desar aquest/a %{model} perquè hi ha %{count} error
|
|
other: No s'ha pogut desar aquest/a %{model} perquè hi ha hagut %{count} errors
|
|
global:
|
|
expand: Desplega
|
|
helpers:
|
|
select:
|
|
default_label: Selecciona...
|
|
prompt: Tria
|
|
search_placeholder: Cerca
|
|
submit:
|
|
create: Crea %{model}
|
|
submit: Desa %{model}
|
|
update: Actualitza %{model}
|
|
number:
|
|
currency:
|
|
format:
|
|
significant: false
|
|
strip_insignificant_zeros: false
|
|
format:
|
|
delimiter: "."
|
|
precision: 3
|
|
round_mode: default
|
|
separator: ","
|
|
significant: false
|
|
strip_insignificant_zeros: false
|
|
human:
|
|
decimal_units:
|
|
format: "%n %u"
|
|
units:
|
|
billion: mil milions
|
|
million:
|
|
one: milió
|
|
other: milions
|
|
quadrillion:
|
|
one: quadrilió
|
|
other: quadrilions
|
|
thousand:
|
|
one: miler
|
|
other: milers
|
|
trillion:
|
|
one: bilió
|
|
other: bilions
|
|
unit: ''
|
|
format:
|
|
delimiter: ''
|
|
precision: 1
|
|
significant: true
|
|
strip_insignificant_zeros: true
|
|
storage_units:
|
|
format: "%n %u"
|
|
units:
|
|
byte:
|
|
one: Byte
|
|
other: Bytes
|
|
eb: EB
|
|
gb: GB
|
|
kb: KB
|
|
mb: MB
|
|
pb: PB
|
|
tb: TB
|
|
percentage:
|
|
format:
|
|
delimiter: ''
|
|
format: "%n %"
|
|
precision:
|
|
format:
|
|
delimiter: ''
|
|
support:
|
|
array:
|
|
last_word_connector: ", i "
|
|
two_words_connector: " i "
|
|
words_connector: ", "
|
|
time:
|
|
am: am
|
|
formats:
|
|
default: "%A, %d de %B de %Y %H:%M:%S %z"
|
|
long: "%d de %B de %Y %H:%M"
|
|
short: "%d de %b %H:%M"
|
|
pm: pm
|