Skip to content

fix(i18n): es-ES names the quote object Cotización everywhere - #1754

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-1530-es-quote-is-cotizacion
Sep 8, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-1530-es-quote-is-cotizacion

Conversation

@os-bill

@os-bill os-bill commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Part of #1530

Deliberately a partial reference, not a closing one — this PR lands the content half of
the ruling only. The ruling's
required closing item, extending the #802 term guard to es-ES, is not in this PR:
it is not mechanical, and the dispatch instructed me to stop and report rather than
redesign a test/** guard that belongs to another epic. Details and evidence below,
so the card stays open for that half.

Ruling executed

Director seat, decision batch #74 (2026-09-07), maintainer reply verbatim 「同意」:
crm_quote is Cotización in es-ES — one term per object, the UI follows the pack,
same direction as #837.

The pack named one object two ways at once. Its declared label was Cotización, but
eight strings called it Presupuesto. A Spanish user reading "Línea de Presupuesto" and
then looking for Presupuesto in the navigation found Cotizaciones, with nothing in the
product saying these were the same record.

The crm_contact help sentence carried the visible symptom the card was filed on: because
it named the masculine el presupuesto, it inflected the statuses to match and told users
to look for Presentado / Aceptado, while the picklist renders Presentada / Aceptada.
The options were right all along — they agree with the feminine Cotización — so they are
untouched; the sentence now names the correct noun.

The 8 changed sites (all quote-sense)

file before after
objects.commerce.ts …en cuanto el presupuesto pasa a Presentado o Aceptado… …en cuanto la cotización pasa a Presentada o Aceptada…
objects.commerce.ts label: 'Línea de Presupuesto' label: 'Línea de Cotización'
objects.commerce.ts pluralLabel: 'Líneas de Presupuesto' pluralLabel: 'Líneas de Cotización'
objects.commerce.ts description: '…dentro de un presupuesto' description: '…dentro de una cotización'
objects.commerce.ts crm_quote: { label: 'Presupuesto' } crm_quote: { label: 'Cotización' }
objects.pipeline.ts quote_accepted: 'Presupuesto Aceptado' quote_accepted: 'Cotización Aceptada'
objects.pipeline.ts label: 'Generar Presupuesto' label: 'Generar Cotización'
objects.pipeline.ts successMessage: '¡Presupuesto creado…' successMessage: '¡Cotización creada…'

Each replacement was applied against an exact anchor asserted to match exactly once,
never by find-and-replace. Diff is 8 changed lines, 8 insertions(+), 8 deletions(-).

Census finding — the keep count is 5, not 3

The ruling gave 8 change + 3 keep. My own enumeration of every presupuest* occurrence
under src/translations/es-ES/** returns 13, graded by referent:

Change — 8, quote-sense. Exactly the ruling's eight; every coordinate confirmed by text.

Keep — 5, budget-sense (the ruling named three of them):

site string referent is machine-evident from
objects.marketing.ts Presupuesto y ROI section key budget
objects.marketing.ts Costo Presupuestado field key budgeted_cost
objects.marketing.ts El coste real supera el coste presupuestado validation key actual_cost_within_budget
objects.pipeline.ts Sin Presupuesto option key no_budget (disqualification reason)
objects.pipeline.ts Sin Presupuesto option key no_budget (loss reason)

The two extra keeps are presupuestado / Presupuestado — the participle "budgeted". An
exact-word grep for Presupuesto does not see them, which is why the ruling counted three.
No change site was missed: the discrepancy is entirely on the keep side, and both extras
are budget-sense. Post-change census: 5 presupuest* remain, all five budget-sense.

This matters beyond bookkeeping — see the guard section: a stem- or substring-grained guard
must spare presupuestado too, not just Presupuesto.

The required closing item is NOT here, and why

The ruling asks to extend test/docs-object-term-consistency.test.ts to es-ES, "using the
guard's SPARED list to keep the budget sense of presupuesto". I read the guard before
judging, as instructed. That shape cannot be built without changing the guard's
assertions
, for four independent reasons — the last one is substantive, not cosmetic:

  1. TERMS is keyed by object name and crm_quote is already taken by the Chinese term.
    A Spanish row for the same object needs a locale dimension in the key.

  2. Pin.hant is mandatory and Chinese-specific (Traditional-script form). Spanish has no
    Traditional form, so the type itself must change.

  3. Every rule hardcodes the Chinese surface — packFor('zh-CN'), PACK_FILE, HANS_PAGES,
    HANT_PAGES, minPages. Measured: content/docs holds 67 .zh-Hans.mdx, 67 .zh-Hant.mdx
    and the English pages, and zero Spanish pages. The entire "the surviving terms are
    actually there" family has no Spanish analogue to assert against.

  4. The guard's criterion provably cannot discriminate the Spanish case. Its central pinned
    rule is "no retired spelling is a substring of spared prose", and its reasoning is that a
    status name is a complete word, so a ledger of complete words discriminates on its own —
    in Chinese, retired 已呈现 versus spared root 到期 are different words. In Spanish the two
    senses are the identical complete word Presupuesto. Retiring it and sparing the budget
    sense makes that rule red by construction:

    collisions = ["Sin Presupuesto contains Presupuesto",
                  "Presupuesto y ROI contains Presupuesto"]
    

    Word grain — the criterion [观察] 文档里的状态名/字段标签与语言包是否一致,无人检查——一周内同一缺陷类出现三次,全靠人眼撞见 #802 measured against three fix commits and deliberately adopted
    over syntactic position — does not reach Spanish. A correct Spanish guard needs a
    referent-aware criterion (judging by the metadata key: no_budget, budget,
    budgeted_cost), which is precisely the position-based criterion that file measured and
    rejected for Chinese. Also SPARED's own first rule checks each phrase against the zh
    pages, so any Spanish phrase added to it is red immediately.

That is a redesign of a test/** guard, not an extension. test/** is epic #1579's declared
territory and the ruling's override reaches only a mechanical closing item, so I stopped here
rather than reshaping it. ja-JP likewise not done (the ruling made it optional): it fails
on reasons 1-3 identically, and the guard already records why the Japanese pack sits outside
the scan surface — 案件 is an ordinary Japanese word used in crm_opportunity's description.

Out-of-scope finding, filed not fixed: the guard's pack rule is vacuous

While reading the guard I found a live defect and proved it. PACK_FILE is
'src/translations/zh-CN.ts', which since the #1311 locale split is a 77-line barrel of 8
imports
holding no pack strings at all — 0 occurrences of 工单, 0 of 报价单, against 56 and
12 in the split files it re-exports. So the rule the guard's own docblock calls out — "the pack
IS the product UI, so a label, an option or a help sentence left behind puts the old word back
on screen"
— scans a file that can never contain a label.

Ablation, on this branch: I re-injected the #1529 defect verbatim into the live pack —
src/translations/zh-CN/objects.commerce.ts, crm_quote.crm_contact.help, 已提交 back to the
retired 已呈现 — proved the mutation on disk (已呈现 0 to 1) and by content hash, and ran the
guard:

VERDICT guard-exit=0    Test Files 1 passed (1)   Tests 33 passed (33)

Green. The guard built to catch exactly that defect no longer catches it. Restored under a
trap and verified byte-identical to HEAD (5e4e25b4…); the tree carries only the three files
of this PR. Filed as its own card, not fixed here — different defect class, and that file is the
epic's ground.

Gates

pnpm verify fully green, exit 0, on the final commit:

validate · typecheck · lint · lint:i18n-gate · hygiene · hygiene:tokens · build · test
✓ i18n lint gate: 0 `i18n/missing-*` issues
✓ source hygiene clean
✓ source token ratchet clean
✓ Build complete (1984ms)
Test Files  163 passed (163)   Tests  3430 passed | 1 skipped (3431)

Changeset written rather than labelled: a language pack is product surface, and these strings
are what a Spanish user reads on screen.


Generated by Claude Code

The es-ES pack called one object two names at once: its declared label was
Cotización while eight strings called it Presupuesto — the generate-quote
action and its success message, the quote_accepted win reason, the whole
crm_quote_line_item object (label, pluralLabel, description and its lookup
back to the quote), and the crm_contact help sentence.

The help sentence also inflected the two status names to agree with the
masculine el presupuesto, so it told users to look for Presentado/Aceptado
while the picklist renders Presentada/Aceptada. The options were correct —
they agree with the feminine Cotización — so they are unchanged; the
sentence now names the right noun.

Presupuesto is also ordinary Spanish for "budget". Every occurrence was
graded by referent rather than swept: the five budget-sense uses
(Presupuesto y ROI, Costo Presupuestado, the campaign cost validation
message, and Sin Presupuesto as a disqualification and a loss reason) are
deliberately untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YKN9TPqDSSXUaYqp2j7MZ
@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 7, 2026 10:55pm UTC

Request Review

@github-actions github-actions Bot added the metadata Declarative metadata — schema, security posture, UI surfaces label Sep 7, 2026
@os-bill
os-bill marked this pull request as ready for review September 8, 2026 00:25
@os-bill
os-bill added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit ebb67fe Sep 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants