rastrillo / fiesta Public

The design system in twelve languages: chrome, explanations and state labels

Every page of the design system now speaks its locale: the chrome
(copy.go's text), the explanations (examples/prose.json, the
password warning included) and the manifest's state labels, through
eleven catalogs, internal/gallery/i18n/<locale>.json, keyed by id. say,
proseIn and label read the catalog and fall back to the English; a
state with no label is not looked up, as an empty prose slot is not.
The language menu names each language by the catalog's
gallery.locale.name, the framework catalogs' own locale_name carried,
read without a lookup in the page's language. newSite refuses a
missing or malformed catalog.

Where the catalogs come from. EnglishStrings lists every id with its
English; go run ./hack/seed/english writes it out; and
hack/seed/run-translations.sh <rastrillo checkout> <commit> runs
TestFiestaTranslations inside a scratch Rastrillo worktree, matching
each string byte for byte against the gallery's prose and sampleProse
tables and the base locale catalogs. Hits are written into the
catalogs, misses into hack/seed/translations-todo.json, and
i18n/drafts.json names every entry that is a machine draft with the
English it was drafted from, so a later run keeps a draft while its
English is unchanged and lists it again once the English moves. Run at
5bb38c0ab31b9e3f5a698c61d73161cd1246471f: 396 English strings, the
same split in every locale.

Carried from Rastrillo, 298 per locale in each of ar, bn, es, ga, hi,
ja, pt-PT, ru, vi, yue and zh-Hans: 288 from prose.go, 7 from
sampleprose.go (gallery.skip, gallery.up.label and the five modal
words gallery.modal.settings, .account, .profile, .billing,
.notifications), 3 from locales/<locale>.toml (gallery.language,
gallery.locale.name, gallery.up).

Machine drafts, 98 per locale in each of the same eleven, made from
the approved English in the register of Rastrillo's catalogs,
identifiers and {placeholders} kept as written. Nobody has reviewed
them. Where the English is a rewrite of a Rastrillo sentence, the
draft starts from Rastrillo's translation of it. The twelve tone
labels (badge, callout and status-pill: negative, neutral, positive,
warning, info) are code words and are kept as written. The ids:

examples: actor.states.an-agent-acting-for-a-person,
api-tokens.blurb, api-tokens.states.four-tokens-two-of-them-live,
api-tokens.states.just-created-the-secret-shown-once,
callout.states.negative-announced-as-an-alert, choice-field.blurb,
confirm-form.states.ordinary-default-cancel-wording,
error-page.states.anything-else-the-generic-pair, field-date.blurb,
field-date.states.enhanced-input-type-tomorrow-or-in-3-weeks,
field-daterange.blurb, field-daterange.states.date-and-time-seeded,
field-datetime.states.enhanced-input-type-tomorrow-or-in-3-weeks,
field-money.blurb, field-select.blurb,
field-select.states.hand-written-with-optgroups,
field-select.states.twelve-options-the-filterable-combobox,
field-text.blurb, field-text.states.the-headline-field,
field-url.blurb, form-error.blurb,
form-foot.states.idle-the-button-before-anything-happens,
form-foot.states.working-what-busy-js-writes-on-the-way-out,
form-layout.blurb, format-dates.blurb, format-money.blurb,
job-status.blurb, job-status.states.running, layout-column.blurb,
list-bar-search.states.empty-accessible-name-from-the-catalog,
list-bar-search.states.with-a-query,
list-bar-search.states.with-a-query-on-page-4, locale-menu.blurb,
locale-menu.states.twelve-languages-current-highlighted,
media-grid.states.screenshots-a-logo-and-a-banner,
media-grid.states.select-mode-one-tile-selected,
meter.states.over-budget, notice.blurb,
pagination.states.first-page-default-accessible-name,
scheme-menu.blurb, scheme-menu.states.standalone,
scheme-menu.states.three-choices-dark-current, seg-tabs.blurb,
shell-topbar.blurb, signin.blurb, signin.states.asking-for-an-address,
signin.states.coming-back-with-keymail,
signin.states.on-the-way-to-keymail,
signin.warnings.email-and-password.body,
stat.states.a-shortened-number, stat.states.the-big-number,
status-pill.states.neutral.
families: choices-and-actions.blurb, form.blurb, screens.blurb,
shells.blurb.
gallery: brand, demo.callout.body, frame.title, icons.lead,
icons.names.body, icons.names.heading, icons.source.body,
overview.intro, routes, routes.getting-started, routes.icons,
started.file.bundle, started.file.busy-js, started.file.fiesta-js,
started.file.sprite, started.file.strings, started.lead,
started.link.body, started.link.heading, started.own.body,
started.own.heading, started.scripts.body, started.stylesheets.body,
started.total, started.upgrading.body, title, tokens.lead,
tokens.note.
labels: badge.negative, badge.neutral, badge.positive, badge.warning,
callout.info, callout.negative, callout.positive, callout.warning,
form-foot.working-what-busy-js-writes-on-the-way-out,
scheme-menu.standalone, status-pill.negative, status-pill.neutral,
status-pill.positive, status-pill.warning.

Pruned from copy.go, since no render asks for them:
gallery.nav.label, gallery.scheme.label (the scheme menu is lifted
from its example and names itself) and gallery.tokens.group.other
(no token falls outside a named group today; one that does now fails
the render by name rather than showing an untranslated heading).

New gates in i18n_test.go, each red by assertion against empty
catalogs before the export:
- TestEveryProseKeyIsTranslated: "i18n/ar.json has no translation of
  examples.actor.blurb (...)".
- TestEveryCopyKeyIsRendered: "copy.go files gallery.nav.label
  ("Pages"), which no render asks for; prune it", and the other two.
- TestNoEnglishProseReachesATranslatedPage, over pages only as
  documentKind classes them, outside [rst-scheme] and the HTML tab's
  source: "day/ar/actor.html carries the English "Choices and
  actions"". Its control, the ja catalog's overview intro put back to
  English, is reported on day/ja/index.html.
- TestNoUnregisteredEnglishInThePageTemplates: "writes "Ag"
  literally" until templateFixtures named the type specimen and the
  icon path's two halves, as Rastrillo's list does.
Already passing, with the named mutation that turns it red:
TestTheLeakSweepSeesEscapedPunctuation (the sweep reading text nodes
in their escaped source form: "the rendered form of "Who did
something, as words: a person's name, ..." is not reported").
Ported, already passing: TestEveryLanguageIsReachableFromThePhoneIndex
in languages_browser_test.go (the Arabic link hidden in the card:
"tab 2 is bn, want the ar link on screen").

Extended:
- TestTheChromeCarriesTheThreeSwitchers: the current language is the
  page's own and each link reads the language's name in itself, as
  the locale-menu example names it. Red first: "the ar link reads
  "English", want "العربية"". The current-mark leg was already true;
  its mutation, English marked current everywhere, reports "the
  language marked current is en".
- TestEveryPageIsAWholeLocalisedDocument: a page kind's <title> names
  it as its catalog does. Red first: "the ar catalog has no name for
  the page, gallery.kind.overview".
- TestEveryLookupIsTraced: an unlabelled state is not looked up; its
  label id is now signin's first state. Mutation: label tracing an
  empty label reports labels.button.default asked for 72 times.
Heights: badge's grouped row document lists its state labels, which
are now translated, and at 320px "No tone (quiet default)" in Russian
wraps: the enforced narrow drive asked for 252px in ru (233 in ar and
ga) against 170. previewMobileHeights["badge/row"] is 252; nothing
else moved, and the pinned bar still fits its reservation in every
locale (53px and 93px against 3.25rem and 6rem).

Preservation, adapted to read the page's language:
TestTheFamilyPagesShowTheFamilyBlurbs and
TestTheGalleryIsTheShippedShell (the back control's words).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 8832dc72d68ebe499b9618a12c6129fe57b78281 parent 4c3a6a9
25 files changed, +7202 −42
  • hack/seed/_rastrillo/translations_fiestaseed_test.go +216 −0
  • hack/seed/english/main.go +50 −0
  • hack/seed/run-translations.sh +73 −0
  • internal/gallery/copy.go +2 −6
  • internal/gallery/gallery.go +6 −2
  • internal/gallery/heights.go +1 −1
  • internal/gallery/i18n.go +153 −16
  • internal/gallery/i18n/ar.json +398 −0
  • internal/gallery/i18n/bn.json +398 −0
  • internal/gallery/i18n/drafts.json +1570 −0
  • internal/gallery/i18n/es.json +398 −0
  • internal/gallery/i18n/ga.json +398 −0
  • internal/gallery/i18n/hi.json +398 −0
  • internal/gallery/i18n/ja.json +398 −0
  • internal/gallery/i18n/pt-PT.json +398 −0
  • internal/gallery/i18n/ru.json +398 −0
  • internal/gallery/i18n/vi.json +398 −0
  • internal/gallery/i18n/yue.json +398 −0
  • internal/gallery/i18n/zh-Hans.json +398 −0
  • internal/gallery/i18n_test.go +549 −0
  • internal/gallery/languages_browser_test.go +113 −0
  • internal/gallery/nav.go +1 −1
  • internal/gallery/prose_test.go +7 −4
  • internal/gallery/rail_test.go +44 −9
  • internal/gallery/tree_test.go +39 −3

CI passed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
ok  	amadan.net/rastrillo/fiesta	0.113s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN   TestTheOneLinkIsUnder25KiBGzipped
    build_test.go:138: fiesta-plain.css: 152856 bytes, 22303 bytes gzipped (21.8 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta-signal.css: 152951 bytes, 22414 bytes gzipped (21.9 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta.css: 155770 bytes, 23018 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.02s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.021s
=== suite ===
--- shard 0 of 15 ---
==> shard 0 of 15: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 go test -count=1 ./...
ok  	amadan.net/rastrillo/fiesta	1.369s
ok  	amadan.net/rastrillo/fiesta/check	6.181s
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	90.692s
ok  	amadan.net/rastrillo/fiesta/example	0.221s
?   	amadan.net/rastrillo/fiesta/hack/seed/english	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.180s
ok  	amadan.net/rastrillo/fiesta/internal/build	1.107s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.019s
ok  	amadan.net/rastrillo/fiesta/internal/gallery	119.868s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.009s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.360s
ok  	amadan.net/rastrillo/fiesta/internal/site	3.373s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.018s
ok  	amadan.net/rastrillo/fiesta/internal/www	78.270s
--- shard 1 of 15 ---
==> shard 1 of 15: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
--- shard 2 of 15 ---
==> shard 2 of 15: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
--- shard 3 of 15 ---
==> shard 3 of 15: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
8:09AM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
8:09AM INF 1 commits scanned.
8:09AM INF scanned ~7199738 bytes (7.20 MB) in 5.12s
8:09AM INF no leaks found
--- shard 4 of 15 ---
==> shard 4 of 15: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok  	amadan.net/rastrillo/fiesta/internal/harness	13.285s
--- shard 5 of 15 ---
==> shard 5 of 15: make browser.check~1of3
./hack/gotest.sh browser 'check~1of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	228.468s
--- shard 6 of 15 ---
==> shard 6 of 15: make browser.check~2of3
./hack/gotest.sh browser 'check~2of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	243.628s
--- shard 7 of 15 ---
==> shard 7 of 15: make browser.check~3of3
./hack/gotest.sh browser 'check~3of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 3 of ./check/ runs 96 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	247.183s
--- shard 8 of 15 ---
==> shard 8 of 15: make browser.internal.gallery~1of4
./hack/gotest.sh browser 'internal.gallery~1of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 4 of ./internal/gallery/ runs 1 of 151 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	315.575s
--- shard 9 of 15 ---
==> shard 9 of 15: make browser.internal.gallery~2of4
./hack/gotest.sh browser 'internal.gallery~2of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 4 of ./internal/gallery/ runs 49 of 151 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	211.731s
--- shard 10 of 15 ---
==> shard 10 of 15: make browser.internal.gallery~3of4
./hack/gotest.sh browser 'internal.gallery~3of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 4 of ./internal/gallery/ runs 51 of 151 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	236.461s
--- shard 11 of 15 ---
==> shard 11 of 15: make browser.internal.gallery~4of4
./hack/gotest.sh browser 'internal.gallery~4of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 4 of 4 of ./internal/gallery/ runs 50 of 151 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	216.483s
--- shard 12 of 15 ---
==> shard 12 of 15: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok  	amadan.net/rastrillo/fiesta/internal/gallery/history	36.367s
--- shard 13 of 15 ---
==> shard 13 of 15: make browser.internal.www~1of2
./hack/gotest.sh browser 'internal.www~1of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	133.768s
--- shard 14 of 15 ---
==> shard 14 of 15: make browser.internal.www~2of2
./hack/gotest.sh browser 'internal.www~2of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	120.858s
hack/seed/_rastrillo/translations_fiestaseed_test.go

Loading diff…

hack/seed/english/main.go

Loading diff…

hack/seed/run-translations.sh

Loading diff…

internal/gallery/copy.go

Loading diff…

internal/gallery/gallery.go

Loading diff…

internal/gallery/heights.go

Loading diff…

internal/gallery/i18n.go

Loading diff…

internal/gallery/i18n/ar.json

Loading diff…

internal/gallery/i18n/bn.json

Loading diff…

internal/gallery/i18n/drafts.json

Loading diff…

internal/gallery/i18n/es.json

Loading diff…

internal/gallery/i18n/ga.json

Loading diff…

internal/gallery/i18n/hi.json

Loading diff…

internal/gallery/i18n/ja.json

Loading diff…

internal/gallery/i18n/pt-PT.json

Loading diff…

internal/gallery/i18n/ru.json

Loading diff…

internal/gallery/i18n/vi.json

Loading diff…

internal/gallery/i18n/yue.json

Loading diff…

internal/gallery/i18n/zh-Hans.json

Loading diff…

internal/gallery/i18n_test.go

Loading diff…

internal/gallery/languages_browser_test.go

Loading diff…

internal/gallery/nav.go

Loading diff…

internal/gallery/prose_test.go

Loading diff…

internal/gallery/rail_test.go

Loading diff…

internal/gallery/tree_test.go

Loading diff…