Problem/Motivation

Yoyaku draws a line between entities that are translatable and entities that are not. yoyaku_tariff_class, yoyaku_allotment, yoyaku_tariff_preset and the placement module section, configuration, grade and pinning entities all declare translatable: TRUE with a data table. yoyaku_resource, yoyaku_slot and yoyaku_resource_tariff declare neither.

Booker-facing prose sits on the wrong side of that line. BookingSlot::label() returns an explicitly typed slot title when a house sets one, and otherwise falls back to the resource label followed by the start time. TransactionSummary puts that string on the cart card, and shows a line tariff label, which is the tariff own label and not its class. So a multilingual house cannot translate its show titles, its typed slot titles or its tariff labels, while it can translate the tariff class heading above them.

Dates are not affected: they are formatted through the date formatter in the interface language.

Proposed resolution

The rule this issue settles on: a string a visitor reads has to be translatable. That makes this a gap to close rather than a line to defend.

The entities in scope are the ones carrying an authored label a booker sees: yoyaku_resource, yoyaku_slot and yoyaku_resource_tariff. Only the label is translated on each; the times, the capacities and quotas, the machine keys, the weights and the references are the same fact in every language. Bookings, transactions, holds, places and pinnings stay untranslated: they are records of what happened and carry no authored prose.

yoyaku_slot_tariff was in the original scope and is now out. It carries no authored prose at all, its fields being the slot, the tariff, the allotment and the quota, so a data table on it would be a row per language of values nothing reads back in one, on the hottest read path there is. What a booker reads on a slot tariff is the resource tariff it provisions.

Two things the work turned up

Translatable storage is only half of it, and the other half had to be part of the same change or the first half delivers nothing a booker can see.

Nothing in the module read these labels in the visitor language. Four call sites in the whole module went through entity.repository, all of them in the placement map, so the entity types that were already translatable had the same defect: a translated section, grade, tariff class or allotment label never reached the cart, the ticket or the offer card. Those are closed in the same pass.

No operator could author a translation of any yoyaku entity. Core attaches its translation overview, its add and edit forms and its Translate tab to an entity type only when that type has a canonical link template, and the yoyaku entities are headless and have none, so translatable: TRUE was data-model-only for every type that carried it. A canonical link now sits on the edit form, which is the screen these entities do have, the way core menu_link_content does it for the one entity type of its own in the same position. Nothing of this appears unless content_translation is installed.

Performance and correctness, measured

Making an entity type translatable relocates its columns from the base table into a field data table, which keeps only the id, the uuid and the langcode behind.

Query counts are the wrong instrument, and the measurement says so plainly: every one of the twenty-six figures in the cost register is unchanged, including the pricing scenarios that were expected to move. A query that reads one of these types now joins or re-reads a data table, and a join is still one query.

What moved is the cost of loading one of these entities cold: a resource from 3 reads to 4, a slot from 1 to 2, a tariff from 2 to 3, being core second read of the data table. It is paid once per distinct entity rather than once per line or per order, so a sweep of twenty orders sharing one slot went from 32.00 to 32.05 queries per order. Read that as a per-request cost rather than as nothing: every cost test builds its fixture in the same request, so the register measures a slot already in the static cache, while a real booking request loads the slot and the tariff cold and pays both reads. Both sides were measured on the same machine minutes apart and are written into docs/performance.md under their own dated heading.

The correctness risk turned out not to be double counting. Core marks any query over a type with a data table non-simple, so a count, a range and a pager all get a GROUP BY on the entity id and a plain fetch is keyed by id. Two other things do bite. Raw SQL naming a moved column on the base table is a hard SQL error, which is what the manager scoping did on every access-checked slot and tariff query. And an unqualified join to the data table carries no langcode, so a slot published in one language reads as published and a list sorted on a label is ordered by one language while showing another; every entity query over the three types now reads the default translation, and the display language is applied where the entity is rendered.

Remaining tasks

  • Agree the measurement plan before any code, since query counts will not show the cost. Done: the cost register re-measured on both sides, plus the cold-load figures above.
  • Make the three entity types translatable and move their field columns.
  • Read those labels in the visitor language on every surface that shows them, mail included, where the request language is the sweep and not the booker.
  • Give a house somewhere to author the translations.
  • Audit every entity query over the affected types for a default langcode condition.
  • Confirm the holding, seating and orphan rule scenarios in the register are unmoved, and record the pricing scenarios before and after.
  • Documentation and French translation in the same change.
  • Still open: the load run over HTTP, which needs the branch deployed and its entity schemas reinstalled.

AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code on the merge request. I reviewed both before posting.)

Issue fork yoyaku-3616524

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

mably’s picture

Status: Active » Needs review
mably’s picture

Title: Booker-facing labels on resources, slots and tariffs cannot be translated » Translate the resource name, the session title and the tariff label a booker reads
Issue summary: View changes
mably’s picture

Title: Translate the resource name, the session title and the tariff label a booker reads » Translate the resource name, the slot title and the tariff label a booker reads
Issue summary: View changes

  • mably committed 7a9ee2c4 on 1.x
    task: #3616524 Translate the resource name, the slot title and the...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.