-
Notifications
You must be signed in to change notification settings - Fork 46
Add mathspeak localization to Mathquill #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Mathquill's screen reader messages, known internally as "mathspeak," have always been hard-coded to English. We are receiving more requests to make them respect the user's language, and this work aims to do just that. Some notable changes include: - Factors out messages to individual English and Spanish catalogs. Note: the latter needs human verification for accuracy but appears to be reasonable enough to keep for testing purposes. - Messages are bundled with the build, so no external files need be distributed. - User can set Mathquill's language: e.g. mq.config({ language: 'es' }). At present, only English and Spanish are recognized, but the system is configured to allow for more, including locale-specific variants. - Dynamic language switching: Default aria label and Mathspeak are recomputed when language changes. Note that user-supplied custom aria labels persist. - Per-instance localization: Each MathField can have independent language settings if desired. - All common symbols, blocks, and structural descriptions are localizable. - Custom fraction, root, and exponent shorthand speech is preserved with some subtle improvements introduced. - Tweaked all mixed-case constructs to be separate words, e.g. "StartFraction" is now "Start Fraction" (tests updated accordingly)
- Added new Mathquill.L10n API and moved the previously global Window functions here - Removed temporary any typings - Updated documentation
var processor = (optionProcessors as any)[name]; // TODO - validate option processors better | ||
(currentOptions as any)[name] = processor ? processor(value) : value; // TODO - think about typing better | ||
|
||
// Handle language changes by updating the global language manager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unclear to me if this is needed? we're already doing it in MQ.config
. I'm just not clear what the codepaths are here
this.revert = function () { | ||
ctrlr.removeMouseEventListener(); | ||
// Clean up language change callback | ||
if (ctrlr.unregisterLanguageChange) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this named unregisterLanguageChange
?
type NodeRef = MQNode | 0; | ||
|
||
// Handle timeout types for both browser and Node.js environments | ||
type TimeoutId = number | NodeJS.Timeout; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this type addition necessary for the localization stuff?
src/locale/es/messages.ftl
Outdated
plus = más | ||
positive = positivo | ||
minus = menos | ||
negative = negativo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty sure this is also menos
in practice. I kind of think we shouldn't be deploying translations that claude generated, especially for math where there's a lot of language patterns which are math-specific. would it be possible to send the english ftl file off to localizers, or just get someone with professional translation experience to review the es translations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,182 @@ | |||
#!/usr/bin/env node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thinking about how we already have a version of this in knox, which is also in typescript. I dunno if it makes sense to try to standardize, mainly just thinking out loud
This also ensures that nodes whose content doesn't update with language changes (specifically binomials, text, and style blocks) are updated by the controller.
- More mathspeak template optimization - Added more localizable messages for block types and brackets - Now using menos instead of negativo to describe negative numbers in Spanish - Removed duplicate strings where possible
const animate = (function () { | ||
// IIFE exists just to hang on to configured rafShim and cancelShim | ||
// functions | ||
// functions. Both return/accept number tokens for cross-environment compatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert this file to main? I'm having trouble seeing how this change is necessary? but maybe it is?
if (this.controller) autoOps = this.controller.options.autoOperatorNames; | ||
const outerThis = this; // Capture outer 'this' for use in callback | ||
return ( | ||
this.foldChildren<string[]>([], function (speechArray, cmd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this were an arrow function we wouldn't need the outerThis
abstraction, would be clearer.
); | ||
LatexCmds.lbrack = bindVanillaSymbol('[', 'left bracket'); | ||
LatexCmds.rbrack = bindVanillaSymbol(']', 'right bracket'); | ||
class LeftBrace extends VanillaSymbol { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the idea that not all of the symbols in here get mathspeak localizations yet? e.g. \\simeq
?
|
||
mathspeak() { | ||
const localization = getControllerLocalization(this); | ||
this.mathspeakTemplate = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this use createMathspeakTemplate?
|
||
mathspeak(opts?: MathspeakOptions) { | ||
const localization = getControllerLocalization(this); | ||
this.mathspeakTemplate = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this use createMathspeakTemplate?
'StartNestedFraction,', | ||
'NestedOver', | ||
', EndNestedFraction' | ||
localization.formatMessage('start-nested-fraction') + ',', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this use createMathspeakTemplate
?
]; | ||
} else { | ||
this.mathspeakTemplate = ['StartFraction,', 'Over', ', EndFraction']; | ||
this.mathspeakTemplate = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this use createMathspeakTemplate?
localization.formatMessage('end-cube-root') | ||
); | ||
} else { | ||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this also use a mathspeak template or do we want to be directly concatenating the string for some reason?
readonly cursor: Cursor; | ||
editable: boolean | undefined; | ||
_ariaAlertTimeout: number; | ||
_ariaAlertTimeout: TimeoutId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this change is not relevant to the PR
textareaSpan: HTMLElement | undefined; | ||
mathspeakSpan: HTMLElement | undefined; | ||
mathspeakId: string | undefined; | ||
unregisterLanguageChange: (() => void) | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still confused about naming here -- what does unregister
mean in this context?
private _domFrag = domFrag(); | ||
selection: MQSelection | undefined; | ||
intervalId: number; | ||
intervalId: TimeoutId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert this to main
__disableGroupingTimeout: number; | ||
textareaSelectionTimeout: number; | ||
__disableGroupingTimeout: TimeoutId; | ||
textareaSelectionTimeout: TimeoutId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can revert this file also
/** Poller that fires once every tick. */ | ||
class EveryTick<Args extends unknown[] = []> { | ||
private timeoutId: number; | ||
private timeoutId: TimeoutId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert this file
I feel this is near the finish line, but after some internal discussion I'm going to close this PR for now as we might have a better long-term way to address this need. |
Description
Mathquill's screen reader messages, known internally as "mathspeak," have always been hard-coded to English. We are receiving more requests to make them respect the user's language, and this work shows how we could fulfill these requests.
Some notable changes include:
Open Questions
This is fairly significant, and it might be worth exploring a dedicated make target which allows external callers to pass in an existing FluentJS bundle.
Note: Claude Code was used for parts of this work, especially in assisting with the refactoring of English messages into the Fluent catalogs and translation into Spanish. I have reviewed and tweaked any new code which was generated along the way.