Problem statement
Many components contain text, either visible or for assistive technology, that supports UX and accessibility and is generally the same across features. Some examples:
- The visually-hidden "Close" label on the Dialog's icon-only close button
- The visually-hidden "Select row" and "Select all rows" checkbox labels in the Table component
- The visually-hidden aria-description on each InputChip, e.g. "Press Enter to edit or Delete to remove", in the ChipInput
Since there is no i18n system in Codex, we require that these strings be passed in as props. In MediaWiki, users can pass in a mw.msg() call to pass in a translated message. Outside of MediaWiki, users must either provide a translatable string somehow, or pass in a hard-coded value.
This is problematic for a few reasons:
- The dev user must provide these props every time, when the messages are usually the same across features
- Extra props are confusing and noisy
- It puts the burden of meeting accessibility criteria on the dev user, when Codex should be providing this by default
We have also used these string props to enable certain features. For example, to enable the Message component's dismiss button, you would think you'd use a boolean prop like useDismissButton. Instead, if you provide the string prop dismissButtonlabel, the dismiss button is enabled. This is counterintuitive.
Implementation
Ideal future state
A better system would include:
- Translated messages for the default values of these common strings
- A way to override these strings if needed (more on this below)
- A way to plug an existing i18n system, such as MediaWiki's, into Codex
- A basic i18n system provided by default in Codex
MVP state
For the most basic solution to this problem, we could rely on MediaWiki's i18n system, and only provide translated strings inside MediaWiki. We would:
- Create a directory of messages for Codex in MediaWiki core (or in Codex if we're able to hook that up to translatewiki easily. If not, we can create the messages in core for now, and move them to Codex later)
- Build a plugin in Codex that injects a provided i18n function* (see proof-of-concept). In core, provide mw.msg().
- Update components with affected props in 1 of 2 ways:
- For props that should never be customized (like the InputChip's aria-description), remove the existing prop and use the composable to seek a provided message. If there isn't one, fall back to a hard-coded English string.
- For props that should be customizable, update the prop to be optional, and add a boolean prop to enable the feature if needed. Then decide which message to show, depending on whether one is provided, whether the prop is provided, or whether the default English string should be used.
*Further implementation notes: We could take inspiration from how Vuetify integrates with vue-i18n, and have the user provide an i18n function (that takes a key + params and returns an i18n-ed string) instead of an object/map; that way we'd provide a lot of flexibility to plug in any i18n system (vue-i18n, MW i18n and a simple map of strings are all easy to integrate this way).
Inventory of affected string props
Note that this needs to be updated, especially with further Table props
This change would allow us to replace the following props with i18n messages:
- ChipInput: removeButtonLabel
- ChipInput: (potentially) a screen reader description explaining how to edit/delete a chip using the keyboard, if we end up adding one as part of T344849
- Dialog: closeButtonLabel (Note: we might need a new prop to allow the close button to be shown/hidden)
- Field/Label: optionalFlag (maybe? we may want to keep allowing arbitrary text here)
- Message: dismissButtonLabel (Note: we would need a new prop to allow the dismiss button to be shown/hidden)
- SearchInput: buttonLabel (maybe?)
- Table: selectAllLabel
- Table: selectRowLabel
- Table: A whole bunch of strings for pagination UI parts
- TypeaheadSearch: searchResultsLabel
- TypeaheadSearch: buttonLabel (maybe?)
Open questions
- How can we build this iteratively, with an initial MVP and then further development?
- How should we prioritize the MVP and the further development?
- What exactly are our end goals for the full solution?
- How will we handle the fact that these are breaking changes?