Skip to content

The playground instrument selector, loading screen and several labels stay in English in the French UI #1762

Open

Description

@joshunrau

Several strings in the playground are hard-coded in English, so the French UI shows English in the instrument selector, on the loading screen and in a few labels. The playground offers English and French through its LanguageToggle and translates most text with t({ en, fr }), but these strings skip it. The instrument selector, the control used most, is the most visible case: in French, its search placeholder, its empty state and all three group headings (Examples, Saved, Templates, which are the raw category values) stay in English. react/jsx-no-literals, which catches this in apps/web and packages/react-core, is not enabled for apps/playground in eslint.config.js.

Where

apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx:33, :44, :46, :48:

aria-label="Load an instrument..."
// ...
<Command.Input placeholder="Search..." />
<Command.List>
  <Command.Empty>No Instruments Found</Command.Empty>
  {Object.entries(categorizedInstruments).map(([heading, instruments]) => (
    <Command.Group heading={heading} key={heading}>

Also:

  • apps/playground/src/App.tsx:13: the loading screen's title="Loading Editor and Toolchain" and subtitle="Please Be Patient, This May Take a While".
  • apps/playground/src/components/Viewer/PreviewFrame.tsx:69: the preview iframe's title="Open Data Capture - Instrument Preview", which screen readers announce.
  • apps/playground/src/components/Editor/EditorPane.tsx:118: the image preview's alt={`Rendering of ${defaultFile.name}`}.
  • apps/playground/src/components/Header/UploadButton/UploadButton.tsx:33,36: the fallback label 'Unlabeled', which becomes the uploaded instrument's visible name.

Reproduce

  1. Open the playground and switch the language toggle to Français.
  2. Open the instrument selector, then type zzz into its search box.
  3. Reload the page.

Actual: the placeholder reads Search..., the headings read Examples, Saved and Templates, the empty state reads No Instruments Found, and the loading screen is in English.
Expected: all of these appear in French.

Tests

Add apps/playground/src/**/*.tsx (excluding stories) to the react/jsx-no-literals block in eslint.config.js, so pnpm lint catches any regression. That lint rule is the guard; the strings themselves have no pure logic to unit-test.

E2E in testing/src/specs/playground.spec.ts: switch to French, open the selector, and assert that the search placeholder and a group heading are the French strings.

Suggested fix

Wrap each string in t({ en, fr }), map the category values to translated headings, and enable react/jsx-no-literals for the playground. The loading screen renders outside CoreProvider, so it needs i18n.t or a small wrapper rather than the hook.

Activity

  1. added
    BugType: existing behavior is wrong
    Priority: LowNice to have, cosmetic or speculative
    Difficulty: LowIsolated change, about 2 hours or less
    Area: Playgroundapps/playground (in-browser instrument editor)
    Good First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributor
    on Oct 7, 2026
  2. joshunrau commented on Oct 26, 2026

    @joshunrau
    CollaboratorAuthor

    Still reproduces on main (d86740e), and no open PR changes these strings. The lint guard, the loading-screen fix and the test plan need adjusting, and a few strings are missing from the list.

    The lint rule catches one of these strings

    The react/jsx-no-literals block sets ignoreProps: true (eslint.config.js:237), so it reports JSX text only. Of the strings listed here, that is just No Instruments Found. aria-label, placeholder, title, subtitle and alt are props, heading={heading} is a variable, and 'Unlabeled' is not JSX. Enabling the rule is still worth it, with three changes:

    • Ignore apps/playground/src/instruments/** as well as stories. The proposed glob matches the seven example-instrument .tsx files, such as examples/interactive/Interactive-With-React/App.tsx, which is full of English JSX text. Those files are instrument source, not playground UI.
    • <span>GitHub</span> at ActionsDropdown.tsx:43 fails. Add GitHub to allowedStrings.
    • The . after {formatByteSize(shareURL.size)} at ShareButton.tsx:61 fails too. Add . to allowedStrings next to : and -, or move the period into the t() call.

    apps/web and packages/react-core have the same gap for props. A guard on literal alt, aria-label, placeholder and title values, for example a no-restricted-syntax selector on JSXAttribute, should cover all three, so it belongs in its own change.

    The loading screen stays in English anyway

    useTranslation does not need CoreProvider. libui's hook reads the module-level i18n singleton, and CoreProvider only renders the notification hub and the destructive-action dialog, so App can call the hook directly.

    The language is not kept across loads, though. main.tsx:12-14 calls i18n.init with no defaultLanguage, and libui 6.16.1 falls back to 'en' (src/i18n/translator.ts:102). Nothing stores the toggle's choice: libui keeps the theme in localStorage, but not the language. The Suspense fallback only shows during the initial load, so after step 3 the whole UI is in English, not just the loading screen. Wrap the two strings anyway, but take the loading screen out of Expected. Remembering the language is a separate decision. The language has to be known when main.tsx calls i18n.init, so it cannot come from the store, which hydrates from IndexedDB asynchronously. A localStorage key would work.

    Missed strings

    None of these is JSX text, so the rule will not flag them either.

    Plan

    Triage note written with Claude Code. Code references point at main @ d86740e.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: Playgroundapps/playground (in-browser instrument editor)BugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessGood First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributorPriority: LowNice to have, cosmetic or speculative

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions