From 1c79a8589dc735d808f5a2d218c94bc8263418fd Mon Sep 17 00:00:00 2001 From: Danil <81031453+Kostenkov-2021@users.noreply.github.com> Date: Tue, 11 Aug 2026 05:03:59 +0500 Subject: [PATCH 1/3] Ru - Improve wording of Russian docs (#661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update acknowledgment for Russian braille * update version number because added Russian braille * UEB has a rule for some pseudo-script chars. The match for that rule was overly broad, so I removed scripted letters and numbers. That rule made its way into other braille codes, so I removed them everywhere. All tests pass. * Added new UEB chars. Many of these are accented letters. I added 'A' (for accented letters) so that the pattern for them looks like: ``` - "Ñ": [t: "CA⠘⠻L⠝"] # 0x00D1 ``` Added a few tests. * Translate German absolute-value ending * Fix German unit plurals * Fix German pluralization for "ruthe" and add regression test * Correct German translations for "ruthe" to "rute" and update regression test * Expand German unit definitions and add regression test for pluralization * fix some symbols * fix german set grammar * typo in example * Russian Braille fixes This commit adds fixes to Russian Braille. Now the test with parentheses is passed. Also there are devision and multiplication signs have been modified according to the Russian braille specs. * Update Russian Braille rules and mappings Fix YAML replace syntax and extend Russian braille rules: add chemical-formula-operator-bond and prefix-geometry-ops rules, broaden function-name matching (include tg/ctg variants) and add separator markers. Update unicode mappings (degree sign, invisible characters, conditional ∪ mapping, parallel/arrow glyphs) and adjust several function/operator braille outputs. Add/expand tests to cover functions, geometry, matrices and chemical formulas to validate the changes. * Add Russian language to docs Add Russian (ru) to the list of supported speech languages and update Russian documentation and translation guidance. - docs/User_guide_for_MathCAT_en.md, docs/index.md: add Russian (ru) to supported languages lists. - docs/ru/User_guide_for_MathCAT_ru.md: include Russian in language options, reorder entries and apply minor wording fixes. - docs/ru/helpers.md: clarify wording, update examples and test instructions to reference ru instead of fr, and improve translation guidance and examples (including test and cargo commands). - docs/ru/index.md: reorder supported languages and polish phrasing. These edits enable and document Russian support and improve clarity for translators and developers working on the Russian localization. * Refactor Russian function braille mapping Replace hardcoded function-name branches in Russian_Rules.yaml with a DefinitionValue lookup that pulls from a new RussianFunctionBraille mapping. Add RussianFunctionBraille entries to definitions.yaml (private-use codepoints U+E100–U+E10E) and map those codepoints to braille strings in unicode.yaml. Add a test covering tan/cot rendering. This centralizes function braille representations and simplifies the rule logic. * Addition of Russian documentation This commit adds Russian documentation for users, developers and translators. * typo in example * Add vector/bar/arc mover rules and tests Introduce three new mover rules in Rules/Braille/Russian/Russian_Rules.yaml: vector-arrow, vector-bar, and geometry-arc. Each rule detects specific accent characters (arrows: → ⟶ ⇀ ⃗; bars: ¯ ‾ _  ̄; arcs: ⌒ ⏜ ︵) and emits the appropriate braille indicators, using a different form when the base has more than one character. Add corresponding tests in tests/braille/Russian/russian.rs to cover single- and multi-character movers and combined expressions. * Add Russian braille math functions and limits Extend Russian braille rules to support additional math functions and large operator limits. Added new rules for large-op-limits, operator/functions (including special handling for div), and div-specific replacements in Rules/Braille/Russian/*. Updated Russian definitions and unicode mappings to include sinh/cosh/tanh/coth, arg/sgn/det/rank/rg, Re/Im, grad/rot/div, gcd/lcm and related braille codes. Updated global Rules/definitions.yaml to recognize new function names. Fixed braille cleanup in src/braille.rs to normalize div encoding. Added unit tests exercising the new function mappings and large operator limits. * Support system left brace in Russian braille Introduce handling for system left-brace matrices in the Russian braille rules: add InSystemLeftBrace variable, a system-left-brace-opener rule and a system-left-brace-table rule to detect brace-enclosed mtable as a system. Update row-start logic to emit appropriate braille tokens when InSystemLeftBrace is set. Add tests exercising curly-brace matrices, piecewise function (brace with conditions), and vertical-bar matrices to verify the new behavior. * Add signed-integer and nested-fraction rules Add handling for nested fractions and signed integers inside script positions and adjust grouping behavior for scripts in Russian braille rules. Changes introduce a nested-fraction rule for mfrac and new signed-integer-number / signed-integer rules for msub/munder and msup/mover, plus a small grouping test (⠐) when scripts contain grouped mrow with '+' or a fraction. Also add a comprehensive regression test (script_grouping_regressions) covering many sup/sub cases to ensure correct braille output. Files updated: Rules/Braille/Russian/Russian_Rules.yaml and tests/braille/Russian/russian.rs. * Enhance Russian Braille support with mathematical symbols and rules (#589) * Refine matrix rule handling by adding support for augmented matrices; update unit tests to include 2x3 and 3x4 augmented matrix scenarios. (#555) * remove logging statement * removed uneeded eprintlin!() * Alternative fix to PR #540. Fixes #528. Includes the tests in the PR. * Add Russian translation * Fix #585 -- SSML was being escaped. I earlier added code to prevent a SSML injection attack by escaping HTML. It was done at the wrong point and escape legal SSML. I have moved the check and conversion down to the leaves. I was very surprised there were no useful tests with SSML. All the main tests set `TTS=None`. I added a test that catches bad escaping. I also added some tests to check for leaf-based SSML attacks and also attribute-based ones. * bump beta number * The range for MATHEMATICAL SANS-SERIF DIGITs had MATHEMATICAL MONOSPACE DIGITs as the match part of translate, so if one of the sans-serif digits were used, you ended up in a loop (no match, to it returned itself). * comment out some debug statements * Add 02DA (˚) to list of circle-like chars -- I thought I had done this already, so it might in some other branch * Add source language option to translation audit tool Allow audit-translations to compare a target language against any source language via --source, defaulting to English for existing workflows. Update CLI output, docs, and tests for dynamic source/target labels, including Swedish/Norwegian comparison coverage. Normalize Rich ANSI output in tests so golden and string assertions remain stable when terminal color is forced. * fix ruff * fix ruff * fix ruff * Core concept names additions and tests -- issue 381 (#592) * initial addition of core concept names * defnitions and tests, with a few failing * minus, plus, volume fixes. Other defined, not appearing in site * fix clippy warnings * use "ruamel.yaml>=0.19.1" in uv.lock and pyproject.toml * handle canonicalize failure in containerized environments (#574) * add 1 week dependency cooldown for uv (#599) * fix rules for digits in german (#596) * update navigation rules for traditional chinese (#588) * copy english version of navigate.yaml from NVDA2026.1 and translate * 更新 definitions.yaml 與 navigate.yaml * update unicode-full.yaml * 區分完全/已完全放大/縮小 * 移至->移到, 表->表格, 數學式->數學, 退出->離開, 不能->無法, 當前->目前。 overview line 47, SimpleSpeak_Rules.yaml line 39, "根號"->""。mroot.rs 對應修改 * 移到上一格 -> 往上一格,移到下一格 -> 往下一格, 佔位符 -> 書籤,設定佔位符 -> 設定書籤,已是最上層 -> 不在別的式子裡面,上一層是 -> 在後面式子裡面,字元模式 開n 次方根 的讀法,基底 -> 基、底 * 向量 -> 射線 * 基 -> 基底,底 -> 基底 * Internally in definitions.yaml, MathCAT uses "nofix", but that's not a MathML airity property. Remove it. * don't say "indexed by", say "sub" * add some terse and verbose forms of speech * fix typo * Added an html file with all the test exprs so that translators have examples to test. * Fix remaining erros after `definitions.yaml` was updated -- needed to add `function:` * fix clippy warning * minor cleanup: remove ASCIIMath-fi reference * Refactor braille.rs to a BrailleCode trait + registry Replace the four scattered string-match dispatch sites (cleanup, get_braille_chars, NeedsToBeGrouped, and highlight knobs) with a single get_braille_code() registry and a BrailleCode trait implemented per code. Extract the duplicated UEB/Vietnam/Finnish/Swedish indicator-replacement closures into one apply_indicator_replacements helper plus a UserTypeforms struct. Behavior is unchanged (all tests/braille pass); adding a new code now means one registry entry + one impl block. Co-authored-by: Cursor * improved counting for (likey) 8 bit codes when figuring out braille position * make sure this is part of repo * add BrailleDocs to repo (stop ignoring) * updated data, does not include ASCII letters/digits * First version of Russian Braille This commit adds first druft version for Russian Braille. * typo in example * Russian Braille fixes This commit adds fixes to Russian Braille. Now the test with parentheses is passed. Also there are devision and multiplication signs have been modified according to the Russian braille specs. * Braille - Latin and Greek letters fixes This commit adds a layer of Latin and Greek alphabets for Russian mathematical Braille. * In unicode.yaml Latin and Greek letters now go through the internal markers of the alphabetic mode. • In braille.rs russian_cleanup turns these markers into real signs: ◦ Latin lowercase: ⠠ ◦ Latin uppercase: ⠨ ◦ Greek lowercase: ⠰ ◦ Greek uppercase: ⠸ Added tests to russian.rs to x+A+y+B, the Latin letter after the number. * Braille - Roots, powers and scripts This commit adds Roots, powers and scripts according to the Russian math Braille specs. * Update Russian Braille rules and mappings Fix YAML replace syntax and extend Russian braille rules: add chemical-formula-operator-bond and prefix-geometry-ops rules, broaden function-name matching (include tg/ctg variants) and add separator markers. Update unicode mappings (degree sign, invisible characters, conditional ∪ mapping, parallel/arrow glyphs) and adjust several function/operator braille outputs. Add/expand tests to cover functions, geometry, matrices and chemical formulas to validate the changes. * Add Russian language to docs Add Russian (ru) to the list of supported speech languages and update Russian documentation and translation guidance. - docs/User_guide_for_MathCAT_en.md, docs/index.md: add Russian (ru) to supported languages lists. - docs/ru/User_guide_for_MathCAT_ru.md: include Russian in language options, reorder entries and apply minor wording fixes. - docs/ru/helpers.md: clarify wording, update examples and test instructions to reference ru instead of fr, and improve translation guidance and examples (including test and cargo commands). - docs/ru/index.md: reorder supported languages and polish phrasing. These edits enable and document Russian support and improve clarity for translators and developers working on the Russian localization. * Refactor Russian function braille mapping Replace hardcoded function-name branches in Russian_Rules.yaml with a DefinitionValue lookup that pulls from a new RussianFunctionBraille mapping. Add RussianFunctionBraille entries to definitions.yaml (private-use codepoints U+E100–U+E10E) and map those codepoints to braille strings in unicode.yaml. Add a test covering tan/cot rendering. This centralizes function braille representations and simplifies the rule logic. * Refine Russian wording in translators guide Clarify and naturalize Russian phrasing in the translators guide: add explicit "на русском языке" to SimpleSpeak examples; change exponent wording to "в квадрате" with example "икс в квадрате"; make fraction wording more natural ("числитель разделить на знаменатель" and example "метр за секунду"); fix "английском" → "английском языке"; and update Braille tooling phrasing to "просмотрщиком Брайля" and "дисплее Брайля" for consistency. * Add alphabet indicators in russian_cleanup Insert a preprocessing step that injects braille alphabet indicators before running REPLACE_INDICATORS. A new helper add_russian_alphabet_indicators scans the raw braille and emits specific indicators for 'l' (Latin lowercase), 'u' (Latin uppercase), 'g' (Greek lowercase) and 'v' (Greek uppercase), avoiding repeated indicators by tracking the current alphabet mode. The helper also resets the mode on 'C', 'N', and '#'. This keeps the existing indicator replacement and space-collapsing logic intact while preventing redundant alphabet indicators. * Russian braille: add GOST symbols and decimals Add GOST-style number and symbol support for Russian braille: introduce a decimal-comma rule that emits BrailleChars for integer and fractional parts with a decimal indicator, using PUA digit placeholders. Extend unicode mappings with bracket/brace/slash/backslash/pipe characters, Celsius, context-sensitive prime (′/″) handling, PUA digit-to-braille mappings, and a few arrow/connective symbols. Update tests to cover decimal numbers, fractions, set notation, Celsius, and logical arrows, and adjust expected output for geometry primes to reflect the new context-aware prime handling. * Add vector/bar/arc mover rules and tests Introduce three new mover rules in Rules/Braille/Russian/Russian_Rules.yaml: vector-arrow, vector-bar, and geometry-arc. Each rule detects specific accent characters (arrows: → ⟶ ⇀ ⃗; bars: ¯ ‾ _  ̄; arcs: ⌒ ⏜ ︵) and emits the appropriate braille indicators, using a different form when the base has more than one character. Add corresponding tests in tests/braille/Russian/russian.rs to cover single- and multi-character movers and combined expressions. * Add Russian braille math functions and limits Extend Russian braille rules to support additional math functions and large operator limits. Added new rules for large-op-limits, operator/functions (including special handling for div), and div-specific replacements in Rules/Braille/Russian/*. Updated Russian definitions and unicode mappings to include sinh/cosh/tanh/coth, arg/sgn/det/rank/rg, Re/Im, grad/rot/div, gcd/lcm and related braille codes. Updated global Rules/definitions.yaml to recognize new function names. Fixed braille cleanup in src/braille.rs to normalize div encoding. Added unit tests exercising the new function mappings and large operator limits. * Support system left brace in Russian braille Introduce handling for system left-brace matrices in the Russian braille rules: add InSystemLeftBrace variable, a system-left-brace-opener rule and a system-left-brace-table rule to detect brace-enclosed mtable as a system. Update row-start logic to emit appropriate braille tokens when InSystemLeftBrace is set. Add tests exercising curly-brace matrices, piecewise function (brace with conditions), and vertical-bar matrices to verify the new behavior. * Add Russian chemistry braille rules and tests Extend Russian braille rules to handle chemical notation: add rules for equilibrium-arrow, reaction operators, repeated charges, charges, and charge-with-subscript mappings (chemical-reaction-operator, chemistry-mhchem-equilibrium-arrow, chemistry-repeated-charge, chemistry-charge, chemistry-charge-with-subscript). Update Rules/Braille/Russian/Russian_Rules.yaml to map arrows, +/-, superscripts/subscripts and reaction operators to appropriate braille sequences. Add tests in tests/braille/Russian/russian.rs (source_chemical_reactions_and_charges) covering reaction arrows, charges, subscripts/superscripts and combined examples. --------- Co-authored-by: Moritz Groß Co-authored-by: nsoiffer Co-authored-by: Tuomas Pyorre <82783219+ttpyorre@users.noreply.github.com> Co-authored-by: V3nky Co-authored-by: Hon-Jang Yang Co-authored-by: nsoiffer Co-authored-by: Cursor * Add Russian braille rules for geometry operators Add three new braille encoding rules for geometry operators (∠, △, ∪) in Russian: - canonical-prefix-geometry-op: handles geometry operators in canonical form - geometry-prefix-after-canonical-multiplier: handles operators after canonical multipliers - geometry-prefix-after-multiplier: handles operators after regular multipliers Includes corresponding test cases for angles, triangles, unions, perpendicular, and similar symbols. * Improve Russian braille typeform and mathvariant handling Refactor Russian braille cleanup to properly handle typeform indicators (bold, italic) and mathvariant attributes. Introduces a dedicated typeform indicator function that manages indicator placement according to Russian Braille ГОСТ Р 58511 standard, separating typeform handling from alphabet indicators. Adds support for automatic typeform indicators based on mathvariant attributes, ensuring proper indicator pairing around content blocks. Updates unicode-full.yaml rules to reflect new typeform codes and adds comprehensive tests for various mathvariant combinations. * Propagate mathvariant attribute to children Add mathvariant inheritance from parent elements to children that lack their own mathvariant attribute. This ensures consistent styling throughout nested elements. Includes tests for bold text propagation through mstyle and mrow containers, and verifies that explicit child mathvariants override inherited values. * Fix clippy warnings and improve code style - Remove unnecessary borrows in format! and display calls - Change string slices to arrays in trim_end_matches calls - Replace expect(&format!(...)) with unwrap_or_else(|_| panic!(...)) - Add clippy allow attributes to suppress false positives in tests - Add missing newline at end of build.rs * Remove unnecessary borrows in format macros Remove redundant `&` reference operators from arguments passed to format! macros and similar functions. These values are automatically borrowed by the format machinery, so explicit borrows are unnecessary and reduce readability. * Normalize Unicode math digits in decimal numbers Add normalization of Unicode mathematical alphanumeric symbols (Plane 1 digits) to ASCII digits for number nodes containing decimal points. Skip canonicalize_plane1 processing for already-normalized nodes to prevent double processing. * Remove unnecessary references and .to_string() calls Remove unnecessary `&` borrows in format arguments and replace `.to_string()` with direct display. Simplify byte array literals to byte string literals for readability and idiomatic Rust style. * Remove mathvariant propagation and update tests Drop implicit propagation of the mathvariant attribute from canonicalization (removed propagate_inherited_mathvariant call and helper functions) and update Russian braille tests to set mathvariant explicitly on elements (replace mstyle with mrow and move attributes to mi). Changes in src/canonicalize.rs and tests/braille/Russian/russian.rs. This makes mathvariant handling explicit in tests and avoids mutating child nodes during canonicalization. * Remove math digit normalization for mn nodes Delete the conditional normalization of mathematical digit characters for text and remove the normalize_math_digits helper from src/canonicalize.rs. Previously the code converted several Unicode math-digit codepoints to ASCII when a decimal point was present. * Adjust test expectations and unignore tests Update expected spoken/braille test outputs across multiple language test files to match updated speech rules (punctuation handling, table phrasing, and unit/roman-numeral rendering). Removed several #[ignore] annotations for the previously skipped "ignore_period_and_space" tests. Minor tidy in pl/definitions.rs to avoid unnecessary format! usage and fix EOF newline. Affected files include tests/Languages/* for en, fi, fr, hu, nb, pl, sv, vi, and zh/tw. * Bump to 0.7.6-beta.8 and update dependencies Update to version 0.7.6-beta.8 with the following changes: - Migrate to no-unsafe variants of sxd-document and sxd-xpath - Add 'no-unsafe' feature flag for unsafe-free builds - Update dependencies: strum, regex, bitflags, env_logger - Add tempfile dev-dependency - Improve build.rs: add rerun-if-changed directives earlier and fix indentation - Exclude BrailleDocs from package * Switch to sxd_document_no_unsafe and NameStr Replace sxd_document/sxd_xpath usages with no-unsafe variants and adapt string types. Key changes in src/braille.rs, src/canonicalize.rs, and src/chemistry.rs: introduce NameStr (feature-gated), use as_str!/as_qname! helpers, convert many attribute_value calls to .as_deref(), update XPath error constructions, and store operator chars as NameStr with PhantomData in OperatorPair. Refactor chemistry tests to helper-based Result flow and use sxd_document_no_unsafe::parser. These edits make the code compatible with the "no-unsafe" pathway and fix lifetime/string mismatches across the codebase. * Use no-unsafe sxd crates; adapt code & tests Replace sxd_document/sxd_xpath with sxd_document_no_unsafe and sxd_xpath_no_unsafe and update imports (including parser). Adjust attribute handling and lifetimes: use as_str!, attribute_value(...).as_deref().unwrap_or_default(), and similar changes to avoid unsafe string lifetimes. Refactor tests to return Result, use ? and panic-catching, and update assertions (e.g. is_empty). Re-enable sample MathML in main, tweak timing_test (disable heavy loops, change loop count). Misc small fixes to satisfy the new APIs and lifetime semantics. * Use no-unsafe sxd libs; refactor TTS & parsing Switch project XML/XPath handling to sxd_document_no_unsafe / sxd_xpath_no_unsafe and adapt code to use as_str!/as_qname! helpers. Update speech and pretty-print logic to use the new context and node APIs, tighten unicode handling, and add debug checks. Refactor TTS APIs so command value accessors return Result and TTS string builders propagate errors. Update tests and prefs helpers (temporary rules dir helper, prefs test flow). Misc: small capacity tweaks and wording fixes in error messages. Files changed: src/prefs.rs, src/pretty_print.rs, src/speech.rs, src/tts.rs. * Improve wording in RU user guide Replace "Трудности в обучении" with "Когнитивные нарушения" in docs/ru/User_guide_for_MathCAT_ru.md to use a more precise and respectful term for users who need shorter spoken output. * Add Russian GOST-58511 typeform braille rules and tests Introduce three Russian GOST-58511 typeform/layout braille rules (small-font, letter-spacing, insertion) to Rules/Braille/Russian/Russian_Rules.yaml with their corresponding braille markers. Expand and reorganize tests in tests/braille/Russian/russian.rs: add coverage for the new typeform indicators, interaction cases, menclose/table/braces, and other edge cases; rename several tests to reflect sectioned/aggregated variants. Also adjust expected braille outputs for a few existing tests (sqrt and geometry matrix cases) to match the new rules/formatting. --------- Co-authored-by: nsoiffer Co-authored-by: mgros Co-authored-by: Tuomas Pyorre <82783219+ttpyorre@users.noreply.github.com> Co-authored-by: V3nky Co-authored-by: Hon-Jang Yang Co-authored-by: nsoiffer Co-authored-by: Cursor --- Rules/Braille/CMU/CMU_Rules.yaml | 30 +- Rules/Braille/Russian/Russian_Rules.yaml | 27 ++ Rules/Braille/Swedish/Swedish_Rules.yaml | 2 +- Rules/Braille/UEB/UEB_Rules.yaml | 2 +- Rules/Braille/UEB/unicode-full.yaml | 35 +- Rules/Braille/UEB/unicode.yaml | 6 +- Rules/Braille/Vietnam/Vietnam_Rules.yaml | 2 +- Rules/Languages/de/ClearSpeak_Rules.yaml | 4 +- Rules/Languages/de/SharedRules/general.yaml | 18 +- Rules/Languages/de/definitions.yaml | 52 ++- Rules/Languages/de/unicode-full.yaml | 305 ++++++++++++-- Rules/Languages/de/unicode.yaml | 18 +- Rules/Languages/en/SharedRules/default.yaml | 29 +- Rules/Languages/en/unicode-full.yaml | 185 ++++++++- Rules/Languages/zh/tw/unicode-full.yaml | 195 +++++++++ build.rs | 8 +- docs/index.md | 2 +- docs/ru/User_guide_for_MathCAT_ru.md | 2 +- docs/ru/helpers.md | 2 +- src/braille.rs | 149 ++++++- src/canonicalize.rs | 7 +- src/chemistry.rs | 1 - src/interface.rs | 6 +- src/navigate.rs | 4 +- src/prefs.rs | 184 ++++----- src/speech.rs | 3 +- tests/Languages/de/ClearSpeak/mfrac.rs | 2 +- tests/Languages/de/ClearSpeak/sets.rs | 22 +- .../de/ClearSpeak/symbols_and_adornments.rs | 18 +- tests/Languages/de/units.rs | 43 +- tests/Languages/en/mtable.rs | 8 +- tests/Languages/en/shared.rs | 11 +- tests/Languages/fi/shared.rs | 3 +- tests/Languages/fr/shared.rs | 3 +- tests/Languages/hu/shared.rs | 3 +- tests/Languages/nb/shared.rs | 3 +- tests/Languages/pl/definitions.rs | 8 +- tests/Languages/pl/shared.rs | 3 +- tests/Languages/sv/shared.rs | 3 +- tests/Languages/vi/vi.rs | 8 +- tests/Languages/zh/tw/shared.rs | 3 +- tests/braille/Russian/russian.rs | 376 +++++++++++++++++- tests/braille/UEB/other.rs | 28 ++ 43 files changed, 1479 insertions(+), 344 deletions(-) diff --git a/Rules/Braille/CMU/CMU_Rules.yaml b/Rules/Braille/CMU/CMU_Rules.yaml index fb53fbeb8..3b5c59a40 100644 --- a/Rules/Braille/CMU/CMU_Rules.yaml +++ b/Rules/Braille/CMU/CMU_Rules.yaml @@ -598,7 +598,7 @@ - name: single-char-exceptions # 4.3.1 tag: msup - match: "*[2][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + match: "*[2][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" replace: - x: "*" - test: @@ -609,7 +609,7 @@ - name: single-char-exceptions # 4.3.1 tag: msubsup - match: "*[3][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + match: "*[3][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" replace: - x: "*[1]" - x: "*[3]" @@ -633,8 +633,8 @@ name: number-char-exceptions # 4.3.1 (FIX: degree doesn't use dot-3 for chemistry or temp/angles/etc) tag: [msub, msup] match: - - "*[2][translate(., \"'*`+-∞ª°²³´¹º‘’“”‟‵‶‷\",'')=''] or" - - "*[2][self::m:mrow and count(*)=2 and *[1][self::m:mn] and *[2][self::m:mo][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']]" + - "*[2][translate(., \"'*`+-∞´°‘’“”‟‵‶‷\",'')=''] or" + - "*[2][self::m:mrow and count(*)=2 and *[1][self::m:mn] and *[2][self::m:mo][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']]" replace: - x: "*[1]" - test: @@ -779,7 +779,7 @@ replace: - x: "*[1]" - test: # special case superscript postscripts that preceed the prescripts - if: "$Postscripts and not($Postscripts[2][self::m:none]) and $Postscripts[2][translate(., \"′″‴⁗'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + if: "$Postscripts and not($Postscripts[2][self::m:none]) and $Postscripts[2][translate(., \"′″‴⁗'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[2]" - test: @@ -805,7 +805,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Prescripts[1][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Prescripts[1][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Prescripts[1][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Prescripts[1]" - test: @@ -835,7 +835,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Prescripts[2][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Prescripts[2][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Prescripts[2][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Prescripts[2]" - test: @@ -868,7 +868,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Prescripts[3][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Prescripts[3][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Prescripts[3][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Prescripts[3]" - test: @@ -898,7 +898,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Prescripts[4][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Prescripts[4][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Prescripts[4][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Prescripts[4]" - test: @@ -935,7 +935,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Postscripts[1][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Postscripts[1][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Postscripts[1][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[1]" - test: @@ -950,7 +950,7 @@ else: - x: "$Postscripts[1]" - test: - if: "not($Postscripts[2][self::m:none]) and $Postscripts[2][translate(., \"′″‴⁗'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')!='']" + if: "not($Postscripts[2][self::m:none]) and $Postscripts[2][translate(., \"′″‴⁗'*`+-´°‘’“”‟‵‶‷\",'')!='']" then: - t: "⠡" - test: @@ -991,7 +991,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Postscripts[3][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Postscripts[3][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Postscripts[3][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[3]" - test: @@ -1021,7 +1021,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Postscripts[4][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Postscripts[4][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Postscripts[4][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[4]" - test: @@ -1054,7 +1054,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Postscripts[5][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Postscripts[5][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Postscripts[5][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[5]" - test: @@ -1084,7 +1084,7 @@ # degree doesn't use dot-3 for chemistry, temp, angles -- can't know that so we make a wild guess here if: "not($Postscripts[6][.='°'] and *[1][self::m:mn or @data-chem-element])" then: [t: "⠄"] - - else_if: "$Postscripts[6][translate(., \"'*`+-ª°²³´¹º‘’“”‟‵‶‷\",'')='']" + - else_if: "$Postscripts[6][translate(., \"'*`+-´°‘’“”‟‵‶‷\",'')='']" then: - x: "$Postscripts[6]" - test: diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml index 0f91068ec..ee9004aed 100644 --- a/Rules/Braille/Russian/Russian_Rules.yaml +++ b/Rules/Braille/Russian/Russian_Rules.yaml @@ -335,6 +335,33 @@ - t: "⠐" - x: "*[2]" +- + name: gost-58511-small-font + tag: [mrow, mstyle, semantics] + match: "@data-braille-typeform='small' or contains(@intent, ':small-font')" + replace: + - t: "⠔" + - x: "*" + - t: "⠔" + +- + name: gost-58511-letter-spacing + tag: [mrow, mstyle, semantics] + match: "@data-braille-typeform='letter-spacing' or contains(@intent, ':letter-spacing')" + replace: + - t: "⠌" + - x: "*" + - t: "⠌" + +- + name: gost-58511-insertion + tag: [mrow, mstyle, semantics] + match: "@data-braille-typeform='insertion' or contains(@intent, ':insertion')" + replace: + - t: "⠧" + - x: "*" + - t: "⠼" + - name: default tag: [mrow, mstyle, semantics] diff --git a/Rules/Braille/Swedish/Swedish_Rules.yaml b/Rules/Braille/Swedish/Swedish_Rules.yaml index 028574914..7a8504574 100644 --- a/Rules/Braille/Swedish/Swedish_Rules.yaml +++ b/Rules/Braille/Swedish/Swedish_Rules.yaml @@ -348,7 +348,7 @@ - name: single-char-exceptions # RUEB 3.11 -- ° and various prime/minute/seconds aren't treated as if superscripts tag: msup - match: "*[2][translate(., \"'*`ª°²³´¹º‘’“”‟′″‴‵‶‷⁗\",'')='']" + match: "*[2][translate(., \"'*`´°‘’“”‟′″‴‵‶‷⁗\",'')='']" replace: - test: if: "NeedsToBeGrouped(*[1], 'Swedish', true())" diff --git a/Rules/Braille/UEB/UEB_Rules.yaml b/Rules/Braille/UEB/UEB_Rules.yaml index b9e9acb79..8e3491c80 100644 --- a/Rules/Braille/UEB/UEB_Rules.yaml +++ b/Rules/Braille/UEB/UEB_Rules.yaml @@ -340,7 +340,7 @@ - name: single-char-exceptions # RUEB 3.11 -- ° and various prime/minute/seconds aren't treated as if superscripts tag: [msup] - match: "*[2][translate(., \"'*`ª°²³´¹º‘’“”‟′″‴‵‶‷⁗\",'')='']" + match: "*[2][translate(., \"'*`´°‘’“”‟′″‴‵‶‷⁗\",'')='']" replace: - test: if: "NeedsToBeGrouped(*[1], 'UEB', true())" diff --git a/Rules/Braille/UEB/unicode-full.yaml b/Rules/Braille/UEB/unicode-full.yaml index 692aad255..373900be3 100644 --- a/Rules/Braille/UEB/unicode-full.yaml +++ b/Rules/Braille/UEB/unicode-full.yaml @@ -1,24 +1,6 @@ --- - - - " ": [t: "W"] # 0x2000 - - " ": [t: "W"] # 0x2001 - - " ": [t: "W"] # 0x2002 - - " ": [t: "W"] # 0x2003 - - " ": [t: "W"] # 0x2004 - - " ": [t: "W"] # 0x2005 - - " ": [t: "W"] # 0x2006 - - " ": [t: "W"] # 0x2007 - - " ": [t: "W"] # 0x2008 - - " ": [t: "W"] # 0x2009 - - " ": [t: "W"] # 0x200a - - "​": [t: "W"] # 0x200b - - "
": [t: "W"] # 0x2028 - - "
": [t: "W"] # 0x2029 - - " ": [t: "W"] # 0x202f - - " ": [t: "W"] # 0x205f - - "": [t: "W"] # 0xfeff - - + - "⋇": [tc: "1⠌⠯⠦"] # 0x22C7 (Division times) + - "⊩": [tc: "⠸⠳⠿⠸⠒"] # 0x22A9 (Forces) - "ℏ": [tc: "⠈⠒⠓"] # t: "B"F (reduced Plank's constant) - "ⅆ": [tc: "⠙"] # 0x2146 - "ⅇ": [tc: "⠑"] # 0x2147 @@ -47,6 +29,7 @@ - "↉": [tc: "#N⠚N⠌N⠑"] # 0x2189 (Vulgar Fraction Zero Thirds) + - "ℊ": [tc: "TL⠛⠁"] # 0x210a (Script Small G) - "ℋ": [tc: "TCL⠓⠁"] # 0x210b (Script Capital H) - "ℒ": [tc: "TCL⠇"] # 0x2113 (Script Capital L) @@ -73,6 +56,16 @@ - "ℤ": [tc: "𝔹CL⠵"] # 0x2124 + - "𝚨-𝛀": # 0x1d6a8 - 0x1d6c0 + - tc: "B" + - spell: "translate('.', '𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚', 'αβγδεζηθικλμνξοπρςστυφχψω')" + + + - "𝛂-𝛚": # 0x1d6c2 - 0x1d6da + - tc: "BGL" + - spell: "translate('.', '𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚', 'αβγδεζηθικλμνξοπρςστυφχψω')" + + - "𝔄-𝔜": # 0x1d504 - 0x1d51d ('z' version is reserved) - tc: "DC" - spell: "translate('.', '𝔄𝔅𝔆𝔇𝔈𝔉𝔊𝔋𝔌𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔𝔕𝔖𝔗𝔘𝔙𝔚𝔛𝔜', 'abcdefghijklmnopqrstuvwxyz')" @@ -106,7 +99,7 @@ # script chars in math alphabetic block and also MathType private use area - "𝒜-𝒵": # 0x1d49c - 0x1d4b5 - tc: "TsC" - - spell: "translate('.', '𝒜𝒝𝒞𝒟𝒠𝒡𝒢𝒣𝒤𝒥𝒦𝒧𝒨𝒩𝒪𝒫𝒬𝒭𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵', 'abcdefghijklmnopqrstuvwxyz')" + - spell: "translate('.', '𝒜𝒝𝒞𝒟𝒠𝒡𝒢𝒣𝒤𝒥𝒦𝒧𝒨𝒩𝒪𝒫𝒬𝒭𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵', 'abcdefghijklmnopqrstuvwxyza')" - "𝒶-𝓏": # 0x1d4b6 - 0x1d4cf - tc: "Ts" diff --git a/Rules/Braille/UEB/unicode.yaml b/Rules/Braille/UEB/unicode.yaml index 3256d59c5..e1b513bc8 100644 --- a/Rules/Braille/UEB/unicode.yaml +++ b/Rules/Braille/UEB/unicode.yaml @@ -103,7 +103,7 @@ - "~": [t: "⠈⠔"] # 0x007E (Tilde (swung dash)) - "¢": [t: "⠈⠉"] # 0x00A2 (Cents sign) - "‐": [t: "⠤"] # 0x2010 (Hyphen) - - "–": [t: "⠠⠤"] # 0x2013 (En Dash) + - "–": [t: "P⠠⠤"] # 0x2013 (En Dash) - "―": [t: "⠐⠠⠤"] # 0x2015 (Horizontal Bar) - "‖": [t: "⠸⠳"] # 0x2016 - "€": [t: "⠈⠑"] # 0x20AC (Euro sign) @@ -123,8 +123,8 @@ - "‴": [t: "1⠶⠶⠶"] # 0x2034 (Triple prime sign) - "℃": [t: "⠘⠚⠠⠉"] # 0x2103 (Degrees Celsius) - "℉": [t: "⠘⠚⠠⠋"] # 0x2109 (Degrees Fahrenheit) - - "Å": [t: "⠠⠘⠫⠁"] # 0x212B (Angstrom symbol) - - "Å": [t: "⠠⠘⠫⠁"] # 0x00C5 (WIRIS uses this for Angstrom) + - "Å": [t: "CA⠘⠫L⠁"] # 0x212B (Angstrom symbol) + - "Å": [t: "CA⠘⠫L⠁"] # 0x00C5 (WIRIS uses this for Angstrom) - "∀": [t: "⠘⠁"] # 0x2200 (For all) - "∂": [t: "⠈⠙"] # 0x2202 (Partial differential) - "∃": [t: "⠘⠢"] # 0x2203 (There exists) diff --git a/Rules/Braille/Vietnam/Vietnam_Rules.yaml b/Rules/Braille/Vietnam/Vietnam_Rules.yaml index 88947d646..2b43067be 100644 --- a/Rules/Braille/Vietnam/Vietnam_Rules.yaml +++ b/Rules/Braille/Vietnam/Vietnam_Rules.yaml @@ -437,7 +437,7 @@ - name: single-char-exceptions # RUEB 3.11 -- ° and various prime/minute/seconds aren't treated as if superscripts tag: msup - match: "*[2][translate(., \"'*`ª°²³´¹º‘’“”‟′″‴‵‶‷⁗\",'')='']" + match: "*[2][translate(., \"'*`´°‘’“”‟′″‴‵‶‷⁗\",'')='']" replace: [x: "*"] - diff --git a/Rules/Languages/de/ClearSpeak_Rules.yaml b/Rules/Languages/de/ClearSpeak_Rules.yaml index ce0f1c3c4..a4953b958 100644 --- a/Rules/Languages/de/ClearSpeak_Rules.yaml +++ b/Rules/Languages/de/ClearSpeak_Rules.yaml @@ -476,7 +476,7 @@ if: "$ClearSpeak_AbsoluteValue = 'AbsEnd'" then: - pause: short - - T: "end" + - T: "ende" - x: "$WordToSay" - pause: short @@ -501,7 +501,7 @@ - T: "Menge" - test: if: $ClearSpeak_Sets != 'woAll' - then: [T: "alle"] + then: [T: "aller"] - x: "*[1]/*[1]" - T: "so dass" - x: "*[1]/*[3]" diff --git a/Rules/Languages/de/SharedRules/general.yaml b/Rules/Languages/de/SharedRules/general.yaml index c64b9e27d..a5792c745 100644 --- a/Rules/Languages/de/SharedRules/general.yaml +++ b/Rules/Languages/de/SharedRules/general.yaml @@ -55,17 +55,21 @@ match: "count(*)=0" replace: - bookmark: "@id" + - test: + - if: "$SpeechStyle = 'ClearSpeak' and ($ClearSpeak_SetMemberSymbol = 'In' or ($ClearSpeak_SetMemberSymbol = 'Auto' and ancestor::m:set)) and preceding::m:mo[1][.='∈' or .='∊' or .='∉']" + then: [T: "den"] + else: [T: "die"] - test: - if: ".='ℂ'" - then: [T: "die komplexen zahlen"] # phrase('the complex numbers' include 2 parts) + then: [T: "komplexen zahlen"] # phrase('the complex numbers' include 2 parts) - else_if: ".='ℕ'" - then: [T: "die natürlichen zahlen"] # phrase('the natural numbers' begin at 1) + then: [T: "natürlichen zahlen"] # phrase('the natural numbers' begin at 1) - else_if: ".='ℚ'" - then: [T: "die rationalen zahlen"] # phrase('the rational numbers' are the fraction of 2 integers) + then: [T: "rationalen zahlen"] # phrase('the rational numbers' are the fraction of 2 integers) - else_if: ".='ℝ'" - then: [T: "die reellen zahlen"] # phrase('the real numbers' can be both positive and negative) + then: [T: "reellen zahlen"] # phrase('the real numbers' can be both positive and negative) - else_if: ".='ℤ'" - then: [T: "die ganzen zahlen"] # phrase('the integers' are natural numbers above 0) + then: [T: "ganzen zahlen"] # phrase('the integers' are natural numbers above 0) else: [x: "text()"] # shouldn't happen - name: real-part @@ -300,7 +304,7 @@ # HACK: '\uF8FE' is used internally for the concatenation char by 'ct' -- this gets the prefix concatenated to the base then: [x: "$Word"] - else_if: "DefinitionValue($Word, 'Speech', 'PluralForms') != ''" - then: [x: "DefinitionValue($Word, 'Speech', 'PluralForms')"] + then: [x: "DefinitionValue($Word, 'Speech', 'PluralForms')", ct: " "] else: [x: "$Word", ct: "s"] else: - x: "$Prefix" @@ -310,7 +314,7 @@ # HACK: '\uF8FE' is used internally for the concatenation char by 'ct' -- this gets the prefix concatenated to the base then: [x: "concat('\uF8FE', $Word)"] - else_if: "DefinitionValue($Word, 'Speech', 'PluralForms') != ''" - then: [x: "concat('\uF8FE', DefinitionValue($Word, 'Speech', 'PluralForms'))"] + then: [x: "concat('\uF8FE', DefinitionValue($Word, 'Speech', 'PluralForms'))", ct: " "] else: [x: "concat('\uF8FE', $Word)", ct: "s"] diff --git a/Rules/Languages/de/definitions.yaml b/Rules/Languages/de/definitions.yaml index 4e5ccf041..0783d9462 100644 --- a/Rules/Languages/de/definitions.yaml +++ b/Rules/Languages/de/definitions.yaml @@ -107,7 +107,7 @@ "in": "zoll", "ft": "fuß", "mi": "meile", - "rd": "ruthe", + "rd": "rute", "li": "glied", "ch": "kette", @@ -159,16 +159,46 @@ } - PluralForms: { - "inch": "zoll", "square inch": "quadratzoll", "cubic inch": "kubikzoll", - "foot": "füße", "square foot": "quadratfuß", "cubic foot": "kubikfuß", - "board foot": "brettfuß", - "degree celsius": "grad celsius", - "degree fahrenheit": "grad fahrenheit", - "hertz": "hertz", - "siemens": "siemens", - "revolution per minute": "umdrehungen pro minute", - "mile per hour": "meilen pro stunde", - "mile per gallon": "meilen pro gallone" + "ampere": "ampere", "candela": "candela", "kelvin": "kelvin", + "gramm": "gramm", "meter": "meter", "mol": "mol", "sekunde": "sekunden", + "becquerel": "becquerel", "coulomb": "coulomb", "grad celsius": "grad celsius", + "farad": "farad", "gray": "gray", "henry": "henry", "hertz": "hertz", + "joule": "joule", "katal": "katal", "lumen": "lumen", "lux": "lux", + "newton": "newton", "ohm": "ohm", "pascal": "pascal", "siemens": "siemens", + "sievert": "sievert", "tesla": "tesla", "volt": "volt", "watt": "watt", "weber": "weber", + "liter": "liter", "tonne": "tonnen", "dalton": "dalton", "neper": "neper", + "atommasse-einheit": "atommasse-einheiten", "elektronenvolt": "elektronenvolt", + "radiant": "radianten", "steradiant": "steradianten", "jahr": "jahre", + "bogenminute": "bogenminuten", "bogensekunde": "bogensekunden", "bit": "bit", + "byte": "byte", "baud": "baud", + "zoll": "zoll", "fuß": "füße", "meile": "meilen", "rute": "ruten", + "glied": "glieder", "kette": "ketten", "fass": "fässer", "gallone": "gallonen", + "quadratzoll": "quadratzoll", "quadratfuß": "quadratfüße", "quadratyard": "quadratyards", + "quadratmeile": "quadratmeilen", "morgen": "morgen", "brettfuß": "brettfüße", + "kubikzoll": "kubikzoll", "kubikfuß": "kubikfüße", "kubikyard": "kubikyards", + "buschelmaß": "buschelmaße", "buschel": "buschel", "teelöffel": "teelöffel", + "esslöffel": "esslöffel", "flüssige dram": "flüssige drams", "gill": "gills", + "tasse": "tassen", "pint": "pints", "quart": "quarts", "korn": "körner", + "dram": "drams", "unze": "unzen", "pfund": "pfund", "zentner": "zentner", + "feingewicht": "feingewichte", "troy-unze": "troy-unzen", "troy-pfund": "troy-pfund", + "ps": "ps", "britische wärmeeinheit": "britische wärmeeinheiten", + "grad fahrenheit": "grad fahrenheit", + "meile pro stunde": "meilen pro stunde", "meile pro gallone": "meilen pro gallone", + + "minute": "minuten", "stunde": "stunden", "tag": "tage", "woche": "wochen", + "grad": "grade", "astronomische einheit": "astronomische einheiten", + "lichtjahr": "lichtjahre", "parsec": "parsec", "ångström": "ångström", + "fermi": "fermis", "hektar": "hektar", "dezibel": "dezibel", + "atmosphäre": "atmosphären", "bar": "bar", "kalorie": "kalorien", "curie": "curie", + "gon": "gon", "mol/liter": "mol/liter", "röntgen": "röntgen", "mho": "mho", + "dyn": "dyn", "erg": "erg", + "kibi-bit": "kibi-bit", "mebi-bit": "mebi-bit", "gibi-bit": "gibi-bit", + "tebi-bit": "tebi-bit", "pebi-bit": "pebi-bit", "exbi-bit": "exbi-bit", + "zebi-bit": "zebi-bit", "yobi-bit": "yobi-bit", + "kibi-byte": "kibi-byte", "mebi-byte": "mebi-byte", "gibi-byte": "gibi-byte", + "tebi-byte": "tebi-byte", "pebi-byte": "pebi-byte", "exbi-byte": "exbi-byte", + "zebi-byte": "zebi-byte", "yobi-byte": "yobi-byte", + "umdrehungen pro minute": "umdrehungen pro minute" } diff --git a/Rules/Languages/de/unicode-full.yaml b/Rules/Languages/de/unicode-full.yaml index a940ceb26..1978993ca 100644 --- a/Rules/Languages/de/unicode-full.yaml +++ b/Rules/Languages/de/unicode-full.yaml @@ -8,8 +8,9 @@ - "¦": [T: "gebrochene Linie"] # 0xa6 (en: 'broken bar') - "§": [T: "Abschnitt"] # 0xa7 (en: 'section') - "¨": [T: "diaeresis"] # 0xa8 (en: 'double dot') - - "©": [T: "Copyright"] # 0xa9 (en: 'copyright') + - "©": [T: "Urheberrecht"] # 0xa9 (en: 'copyright') - "ª": [T: "weibliches Ordnungszeichen"] # 0xaa (en: 'feminine ordinal indicator') + - "¬": [T: "nicht"] # 0xac (en: 'not') - "«": [T: "Nach links zeigendes doppeltes spitzes Anführungszeichen"] # 0xab (en: 'left-pointing double angle quote mark', MathPlayer: 'left-pointing double angle quote mark', google: 'links zeigende doppelwinkel-zitatmarke') - "¯": # 0xaf - test: @@ -18,9 +19,24 @@ else: [T: "Überstrich"] # (en: 'line', MathPlayer: 'macron', google: 'linie') - "²": [T: "Quadrat"] # 0xb2 (en: 'two', MathPlayer: 'zum Quadrat', google: 'zwei') - "³": [T: "Kubik"] # 0xb3 (en: 'three', MathPlayer: 'hoch drei', google: 'drei') + - "´": [T: "Akutakzent"] # 0xb4 (en: 'acute', MathPlayer: 'acute accent', google: 'akut') - "µ": [T: "Mikrozeichen"] # 0xb5 (en: 'micro', MathPlayer: 'micro sign', google: 'mikro') - "¹": [T: "hoch eins"] # 0xb9 (en: 'one', google: 'eins') - "º": [T: "Männliches Ordnungszeichen"] # 0xb9 (en: 'masculine ordinal indicator') + - "·": + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolDot = 'Auto'" + then: [T: "mal"] # (en: 'times', google translation) + else: [T: "mal"] # (en: 'dot', MathPlayer: 'Punkt mittig', google: 'punkt') + - "×": # 0xd7 + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolX = 'Auto'" + then: [T: "mal"] # (en: 'times', google translation) + else_test: + if: $ClearSpeak_MultSymbolX = 'By' + then: [T: "von"] # (en: 'by', google translation) + else: [T: "mal"] # (en: 'cross', MathPlayer: 'times', google: 'kreuzen') + - "÷": [T: "geteilt durch"] # 0xf7 (en: 'divided by', MathPlayer: 'divides') - "¡": [T: "umgekehrtes ausrufezeichen"] # 0xa1 (en: 'inverted exclamation mark') - "¶": [T: "absatzzeichen"] # 0xb6 (en: 'paragraph mark') - "¿": [T: "umgekehrtes fragezeichen"] # 0xbf (en: 'inverted question mark') @@ -108,6 +124,14 @@ - "˽": [T: "modifikatorregal"] # 0x2fd (en: 'modifier shelf', google translation) - "˾": [T: "modifikator offenes regal"] # 0x2fe (en: 'modifier open shelf', google translation) - "˿": [T: "modifikator niedriger links pfeil"] # 0x2ff (en: 'modifier low left arrow', google translation) + - "̀": [T: "gravisakzentverzierung"] # 0x300 (en: 'grave accent embellishment', google: 'grabakzentverzierung') + - "́": [T: "akutakzentverzierung"] # 0x301 (en: 'acute accent embellishment', google: 'akute akzentverzierung') + - "̂": [T: "Hutverzierung"] # 0x302 (en: 'circumflex accent embellishment', google: 'zirma akzentverzierung') + - "̃": [T: "tildeverzierung"] # 0x303 (google: 'tilde verzierung') + - "̄": [T: "makronverzierung"] # 0x304 (en: 'macron embellishment', google: 'makron -verzierung') + - "̅": [T: "Überstreifenverzierung"] # 0x305 (google: 'überbärerverzierung') + - "̆": [T: "kombinierendes Breve"] # 0x306 (en: 'breve embellishment', google: 'breve -verzierung') + - "̇": [T: "Punkt über Verzierung"] # 0x307 (google: 'punkt über verzierung') - "̈": [T: "zwei Punkte oberhalb"] # 0x308 (en: 'diaeresis embellishment', google: 'diaeresis -verzierung') - "̉": [T: "haken über verzierung"] # 0x309 (en: 'hook above embellishment', google translation) - "̊": [T: "ring über verzierung"] # 0x30a (en: 'ring above embellishment', google: 'ring über verzierung') @@ -197,9 +221,13 @@ - "ϒ": [T: "Variante Upsilon mit Haken"] # 0x3d2 (en: 'upsilon with hook', google: 'upsilon mit haken') - "ϓ": [T: "Upsilon mit Akutakzent und Haken"] # 0x3d3 (en: 'upsilon with acute and hook', google translation) - "ϔ": [T: "Upsilon mit Diaeresis und Haken"] # 0x3d4 (en: 'upsilon with diaeresis and hook', google translation) + - "ϕ": [T: "gerades Phi"] # 0x3d5 (en: 'phi', google: 'phi') + - "ϖ": [T: "Variante Pi"] # 0x3d6 (en: 'pi', google: 'pi') - "ϗ": [T: "kai"] # 0x3d7 (MathPlayer: 'variant kai') - "Ϙ": [T: "großes archaisches koppa"] # 0x3d8 (en: 'cap archaic koppa') - "ϙ": [T: "archaisches koppa"] # 0x3d9 (en: 'archaic koppa') + - "ϵ": [T: "epsilon"] # 0x3f5 + - "϶": [T: "umgekehrtes epsilon"] # 0x3f6 (en: 'reversed epsilon', MathPlayer: 'reversed epsilon', google: 'umgekehrter epsilon') - "А-Я": # 0x410 - 0x42f - test: if: "$CapitalLetters_Beep" @@ -256,6 +284,10 @@ - "‐": [T: "Bindestrich"] # 0x2010 (en: 'hyphen', MathPlayer: 'minus', google: 'bindestrich') - "‑": [T: "nicht-trennender Bindestrich"] # 0x2011 (en: 'hyphen', google: 'bindestrich') - "‒": [T: "Figurenstrich"] # 0x2012 (en: 'figure dash', google: 'figur dash') + - "–": [T: "Halbgeviertstrich"] # 0x2013 (SRE: 'En Dash') + - "—": [T: "Geviertstrich"] # 0x2014 (SRE: 'EM Dash') + - "―": [T: "Horizontale Linie"] # 0x2015 (en: 'horizontal bar', MathPlayer: 'horizontal bar', google: 'horizontale linie') + - "‖": [T: "Doppelte vertikale Linie"] # 0x2016 (en: 'double vertical line', MathPlayer: 'double vertical bar', google: 'doppelte vertikale linie') - "†": [T: "Dolch"] # 0x2020 (en: 'dagger', MathPlayer: 'dagger', google: 'dolch') - "‡": [T: "Doppelter Dolch"] # 0x2021 (en: 'double dagger', MathPlayer: 'double dagger', google: 'doppeldolch') @@ -267,8 +299,24 @@ then: [T: "punkt"] # (en: 'dot', google translation) else: [T: "Kugel"] # (en: 'bullet', MathPlayer: 'bullet', google: 'kugel') + - "…": # 0x2026 + test: + if: + - "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_Ellipses = 'Auto' or" + # must be ClearSpeak and $ClearSpeak_Ellipses = 'AndSoOn' + # speak '…' as 'and so on...' unless expr starts with '…' + - "../*[1][text()='…']" + then: [T: "punkt punkt punkt"] # (en: 'dot dot dot', google translation) + else_test: # must have $ClearSpeak_Ellipses = 'AndSoOn' + if: "count(following-sibling::*) = 0" + then: [T: "und so weiter"] # (en: 'and so on', google translation) + else: [T: "horizontale Ellipse"] # (en: 'and so on up to', MathPlayer: 'dot dot dot', google: 'und so weiter bis') + - "‰": [T: "promille"] # 0x2030 (en: 'per mille', MathPlayer: 'per mille sign', google: 'pro mille') - "‱": [T: "pro Zehntausend"] # 0x2031 (en: 'per ten thousand', MathPlayer: 'per ten thousand sign', google: 'pro zehntausend') + - "′": [T: "Strich"] # 0x2032 (en: 'prime', MathPlayer: 'strich', google: 'prime') + - "″": [T: "zwei Striche"] # 0x2033 (en: 'double prime', MathPlayer: 'zwei-strich', google: 'double prime') + - "‴": [T: "drei Striche"] # 0x2034 (en: 'triple prime', MathPlayer: 'drei-strich', google: 'triple prime') - "‵": [T: "umgekehrter Strich"] # 0x2035 (en: 'reversed prime', MathPlayer: 'reversed prime', google: 'umgekehrt prime') - "‶": [T: "zwei umgekehrte Striche"] # 0x2036 (en: 'reversed double prime', MathPlayer: 'reversed double prime', google: 'umgekehrt doppelte prime') - "‷": [T: "drei umgekehrte Striche"] # 0x2037 (en: 'reversed triple prime', google: 'triple prime umgekehrt') @@ -398,6 +446,7 @@ - "℆": [T: "cada una"] # 0x2106 (google translation) - "ℇ": [T: "Euler-Konstante"] # 0x2107 (en: 'euler's constant', google: 'eulers konstante') - "℈": [T: "skrupel"] # 0x2108 (en: 'scruples', google translation) + - "℉": [T: "Grad Fahrenheit"] # 0x2109 (google: 'grad fahrenheit') - "ℊ": [T: "Skript G"] # 0x210a (google: 'skript g') - "ℌℑℨℭ": # 0x210c, 0x2111, 0x2128, 0x212d - T: "Fraktur" # (en: 'fraktur', google: 'fraktur') @@ -409,6 +458,7 @@ then: [T: "doppelt geschlagen"] # (en: 'double struck', google translation) - spell: "translate('.', 'ℍℙℾℿ', 'HPΓΠ')" + - "ℎ": [T: "Planck-Konstante"] # 0x210e (en: 'planck constant', MathPlayer: 'planck constant', google: 'planck konstant') - "ℏ": # 0x210f - test: if: "($Verbosity='Terse')" @@ -419,11 +469,15 @@ - T: "Skript" # (en: 'script', MathPlayer: 'h bar', google: 'skript') - spell: "translate('.', 'ℐℒ℘ℬℰℱℳ', 'ILPBEFM')" + - "ℓ": [T: "Schreibschrift l"] # 0x2113 (en: 'script l', MathPlayer: 'script l', google: 'skript l') - "℔": [T: "L B Balkensymbol"] # 0x2114 (en: 'pounds', google: 'pfund') - "№": [T: "Numero-Zeichen"] # 0x2116 (en: 'number', MathPlayer: 'numero sign', google: 'nummer') - "℥": [T: "Unze-Zeichen"] # 0x2125 (en: 'ounces', MathPlayer: 'ounce sign', google: 'unzen') + - "Ω": [T: "Ohm-Zeichen"] # 0x2126 (en: 'ohms', MathPlayer: 'ohm sign', google: 'ohm') - "℧": [T: "Umgekehrtes Ohm-Zeichen"] # 0x2127 (en: 'mhos', MathPlayer: 'inverted ohm', google: 'mhos') - "℩": [T: "gedrehtes Iota"] # 0x2129 (google: 'drehte iota') + - "K": [T: "Kelvin-Zeichen"] # 0x212a (en: 'kelvin', MathPlayer: 'degrees kelvin', google: 'kelvin') + - "Å": [T: "Angstrom-Zeichen"] # 0x212b (en: 'angstroms', MathPlayer: 'angstroms', google: 'angstrome') - "ℯ": [T: "Skript E"] # 0x212f (google: 'skript e') # coalesced some chars that use cap letters @@ -501,6 +555,10 @@ - "ⅾ": [T: "ⅾ"] # 0x217e (en: 'D', google translation) - "ⅿ": [T: "ⅿ"] # 0x217f (en: 'M', google translation) - "↉": [T: "null drittel"] # 0x2189 (en: 'zero thirds') + - "←": [T: "Pfeil nach links"] # 0x2190 (en: 'leftwards arrow', MathPlayer: 'leftwards arrow', google: 'pfeil nach links') + - "↑": [T: "Aufwärtspfeil"] # 0x2191 (en: 'upwards arrow', MathPlayer: 'upwards arrow', google: 'nach oben pfeil') + - "→": [T: "Pfeil nach rechts"] # 0x2192 (en: 'rightwards arrow', MathPlayer: 'rightwards arrow', google: 'richtiger pfeil') + - "↓": [T: "Pfeil nach unten"] # 0x2193 (en: 'downwards arrow', MathPlayer: 'downwards arrow', google: 'nach unten pfeil') - "↔": [T: "Pfeil nach links und rechts"] # 0x2194 (en: 'left right arrow', MathPlayer: 'left right arrow', google: 'links rechts pfeil') - "↕": [T: "Pfeil nach oben und unten"] # 0x2195 (en: 'up down arrow', MathPlayer: 'up down arrow', google: 'auf pfeil runter') - "↖": [T: "Nordwestpfeil"] # 0x2196 (en: 'north west arrow', MathPlayer: 'north west arrow', google: 'nordwestpfeil') @@ -573,6 +631,7 @@ - "⇏": [T: "Doppelpfeil nach rechts mit Strich"] # 0x21cf (en: 'rightwards double arrow with stroke', MathPlayer: 'rightwards double arrow with stroke', google: 'richtiger doppelpfeil mit schlaganfall') - "⇐": [T: "Doppelpfeil nach links"] # 0x21d0 (en: 'leftwards double arrow', MathPlayer: 'leftwards double arrow', google: 'links doppelpfeil') - "⇑": [T: "Doppelpfeil nach oben"] # 0x21d1 (en: 'upwards double arrow', MathPlayer: 'upwards double arrow', google: 'double -pfeil nach oben') + - "⇒": [T: "Doppelpfeil nach rechts"] # 0x21d2 (en: 'rightwards double arrow', MathPlayer: 'rightwards double arrow', google: 'richtiger doppelpfeil') - "⇓": [T: "Doppelpfeil nach unten"] # 0x21d3 (en: 'downwards double arrow', MathPlayer: 'downwards double arrow', google: 'downwards doppelpfeil') - "⇔": [T: "Linker rechter Doppelpfeil"] # 0x21d4 (en: 'left right double arrow', MathPlayer: 'left right double arrow', google: 'links rechts doppelpfeil') - "⇕": [T: "Doppelpfeil nach oben und unten"] # 0x21d5 (en: 'up down double arrow', MathPlayer: 'up down double arrow', google: 'doppelpfeil nach oben') @@ -624,18 +683,107 @@ if: "$Verbosity!='Terse'" then: [T: "die"] # (en: 'the', google translation) - T: "Komplement" # (en: 'complement of') + - "∂": # 0x2202 + - test: + if: "$Verbosity='Terse'" + then: [T: "partiell"] # (en: 'partial', google translation) + else: [T: "partielle Ableitung"] # (en: 'partial derivative', google: 'partielle ableitung') - "∃": [T: "es gibt"] # 0x2203 (en: 'there exists') - "∄": [T: "es gibt nicht"] # 0x2204 (en: 'there does not exist') - "∅": [T: "leere Menge"] # 0x2205 (en: 'empty set') + - "∆": # 0x2206 + - test: + if: "$Verbosity!='Terse'" + then: [T: "die"] # (en: 'the', google translation) + - T: "Laplace-Operator von" # (en: 'laplacian of', MathPlayer: 'increment', google: 'laplace von') + - "∇": # 0x2207 + - test: + if: "$Verbosity!='Terse'" + then: [T: "die"] # (en: 'the', google translation) + - T: "Nabla" # (en: 'gradient of', google: 'gradient von') + - "∈": # 0x2208 + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "ein Element von"] # (en: 'an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "in"] # (google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "Element von"] # (en: 'member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "Element von"] # (en: 'element of', google translation) + - else: [T: "Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "ist ein Element von"] # (en: 'is a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "ist ein Element von"] # (en: 'is an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "ist in"] # (en: 'is in', google translation) + - else: [T: "Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belongs to') + - "∉": # 0x2209 + # rule is identical to 0x2208 + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "ist kein Element von"] # (en: 'is not an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "nicht in"] # (en: 'not in', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "kein Element von"] # (en: 'not member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "kein Element von"] # (en: 'not element of', google translation) + - else: [T: "nicht Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'not belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "ist kein Element von"] # (en: 'is not a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "ist kein Element von"] # (en: 'is not an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "ist nicht in"] # (en: 'is not in', google translation) + - else: [T: "nicht Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'does not belong to') + - "∊": # 0x220a + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "ist ein Element von"] # (en: 'is an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "in"] # (google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "Element von"] # (en: 'member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "Element von"] # (en: 'element of', google translation) + - else: [T: "Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "ist ein Element von"] # (en: 'is a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "ist ein Element von"] # (en: 'is an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "ist in"] # (en: 'is in', google translation) + - else: [T: "Element von"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belongs to') - "∋": [T: "hat als Element"] # 0x220b (en: 'contains the member') - "∌": [T: "hat nicht als Element"] # 0x220c (en: 'does not contain the member') - "∍": [T: "hat als Element"] # 0x220d (en: 'contains the member') - "∎": [T: "Ende Beweis"] # 0x220e (en: 'end of proof') + - "∏": [T: "Produkt"] # 0x220f (en: 'product', MathPlayer: 'Product', google: 'produkt') + - "∐": [T: "Koprodukt"] # 0x2210 (en: 'coproduct', MathPlayer: 'coproduct', google: 'koprodukt') + - "∑": [T: "Summe"] # 0x2211 (en: 'sum') - "−": [T: "minus"] # 0x2212 - "∓": [T: "minus plus"] # 0x2213 (en: 'minus or plus') - "∔": [T: "Plus mit Punkt"] # 0x2214 (en: 'dot plus', MathPlayer: 'dot plus', google: 'punkt plus') - "∕": [T: "Division"] # 0x2215 (en: 'divided by', MathPlayer: 'Schrägstrich (Division)', google: 'geteilt durch') - "∖": [T: "Mengendifferenz"] # 0x2216 (en: 'set minus', MathPlayer: 'Mengen-Minus', google: 'minus einstellen') + - "∗": [T: "Sternoperator"] # 0x2217 (en: 'times', MathPlayer: 'asterisk operator', google: 'mal') - "∘": [T: "verknüpft mit"] # 0x2218 (en: 'composed with') - "∙": # 0x2219 - test: @@ -643,6 +791,11 @@ then: [T: "punkt"] # (en: 'dot', google translation) else: [T: "Kreisoperator"] # (en: 'times', MathPlayer: 'bullet operator', google: 'mal') + - "√": # 0x221a + - test: + if: "$Verbosity!='Terse'" + then: [T: "die"] # (en: 'the', google translation) + - T: "Quadratwurzel" # (en: 'square root of', MathPlayer: 'radical', google: 'quadratwurzel von') - "∛": # 0x221b - test: if: "$Verbosity!='Terse'" @@ -653,12 +806,26 @@ if: "$Verbosity!='Terse'" then: [T: "die"] # (en: 'the', google translation) - T: "vierte Wurzel" # (en: 'fourth root of') + - "∝": # 0x221d + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "proportional zu" # (en: 'proportional to') - "∞": [T: "unendlich"] # 0x221e (en: 'infinity') + - "∟": [T: "rechter Winkel"] # 0x221f (en: 'right angle') + - "∠": [T: "Winkel"] # 0x2220 (en: 'angle') + - "∡": [T: "gerichteter Winkel"] # 0x2221 (en: 'measured angle', MathPlayer: 'measured angle', google: 'gemessener winkel') - "∢": [T: "Raumwinkel"] # 0x2222 (en: 'spherical angle', MathPlayer: 'spherical angle', google: 'sphärischer winkel') - "∣": [T: "teilt"] # 0x2223 (en: 'divides') - "∤": [T: "teilt nicht"] # 0x2224 (en: 'does not divide') - "∧": [T: "und"] # 0x2227 (en: 'and') - "∨": [T: "oder"] # 0x2228 (en: 'or') + - "∩": [T: "Durchschnitt"] # 0x2229 (en: 'intersection') + - "∪": [T: "Vereinigung"] # 0x222a (en: 'union') + - "∫": [T: "Integral"] # 0x222b (en: 'integral') + - "∬": [T: "Doppelintegral"] # 0x222c (en: 'double integral') + - "∭": [T: "Dreifachintegral"] # 0x222d (en: 'triple integral', MathPlayer: 'Dreifaches Integral', google: 'dreifachintegral') + - "∮": [T: "Randintegral"] # 0x222e (en: 'contour integral', MathPlayer: 'Kontur-Integral', google: 'konturintegral') - "∯": [T: "Oberflächenintegral"] # 0x222f (en: 'surface integral', MathPlayer: 'Flächenintegral', google: 'oberflächenintegral') - "∰": [T: "Volumenintegral"] # 0x2230 (en: 'volume integral', MathPlayer: 'Volumsintegral', google: 'volumenintegral') - "∱": [T: "Integral im Uhrzeigersinn"] # 0x2231 (en: 'clockwise integral') @@ -666,6 +833,12 @@ - "∳": [T: "Kurvenintegral im Gegenuhrzeigersinn"] # 0x2233 (en: 'anticlockwise contour integral', MathPlayer: 'Kontur-Integral im Gegenuhrzeigersinn', google: 'konturintegral gegen den gegenwart') - "∴": [T: "folglich"] # 0x2234 (en: 'therefore', MathPlayer: 'deshalb', google: 'daher') - "∵": [T: "weil"] # 0x2235 (en: 'because') + - "∶": # 0x2236 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "Verhältnis" # (en: 'to') + - "∷": [T: "Proportion"] # 0x2237 (en: 'as') - "∸": [T: "Minus mit Punkt"] # 0x2238 (en: 'dot minus', MathPlayer: 'dot minus', google: 'punkt minus') - "∹": [T: "Überschuss"] # 0x2239 (en: 'has excess compared to', MathPlayer: 'excess', google: 'hat überschüssig im vergleich zu') - "∺": # 0x223a @@ -678,6 +851,14 @@ if: "$Verbosity!='Terse'" then: [T: "ist"] # (en: 'is', google translation) - T: "homothetisch" # (en: 'homothetic to', MathPlayer: 'homothetic', google: 'homothetisch zu') + - "∼": [T: "Tildeoperator"] # 0x223c (en: 'varies with', MathPlayer: 'tilde operator', google: 'variiert mit') + - "∽": [T: "Umgekehrte Tilde"] # 0x223d (en: 'reversed tilde', MathPlayer: 'reversed tilde', google: 'umgekehrte tilde') + - "∾": # 0x223e + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "Umgekehrtes stummes S" # (en: 'most positive', MathPlayer: 'inverted lazy s', google: 'am positivsten') + - "∿": [T: "Sinuswelle"] # 0x223f (en: 'sine wave', MathPlayer: 'sine wave', google: 'sinus') - "≀": [T: "Kranzprodukt"] # 0x2240 (en: 'wreath product', MathPlayer: 'wreath product', google: 'kranzprodukt') - "≁": [T: "durchgestrichene Tilde"] # 0x2241 (en: 'not tilde', MathPlayer: 'not tilde', google: 'nicht tilde') - "≂": [T: "minus tilde"] # 0x2242 (SRE: 'Minus über Tilde') @@ -778,6 +959,16 @@ then: [T: "ist"] # (en: 'is', google translation) - T: "gemessen mit" # (en: 'measured by', MathPlayer: 'measured by') - "≟": [T: "vielleicht gleich"] # 0x225f (en: 'has an unknown relationship with', MathPlayer: 'questioned equal to', google: 'hat eine unbekannte beziehung zu') + - "≠": # 0x2260 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "ungleich" # (en: 'not equal to') + - "≡": # 0x2261 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "identisch mit" # (en: 'identical to', SRE: 'kongruent mit') - "≢": # 0x2262 - test: if: "$Verbosity!='Terse'" @@ -888,6 +1079,36 @@ - "≿": [T: "nachfolgend oder äquivalent"] # 0x227f (en: 'succeeds or is equivalent to', MathPlayer: 'succeeds or equivalent to', google: 'erfolg oder ist gleichbedeutend mit') - "⊀": [T: "nicht vorangehend"] # 0x2280 (en: 'does not precede', MathPlayer: 'does not precede', google: 'vorausgegangen nicht voraus') - "⊁": [T: "nicht nachfolgend"] # 0x2281 (en: 'does not succeed', MathPlayer: 'does not succeed', google: 'gelingt es nicht') + - "⊂": # 0x2282 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist ein"] # (en: 'is a', google translation) + - T: "echte Teilmenge von" # (en: 'subset of', MathPlayer: 'Teilmenge von', google: 'untergruppe von') + - "⊃": # 0x2283 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist ein"] # (en: 'is a', google translation) + - T: "echte Obermenge von" # (en: 'superset of', MathPlayer: 'Obermenge von', google: 'superset von') + - "⊄": # 0x2284 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "keine echte Teilmenge von" # (en: 'not a subset of', MathPlayer: 'nicht Teilmenge von', google: 'keine teilmenge von') + - "⊅": # 0x2285 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist"] # (en: 'is', google translation) + - T: "keine echte Obermenge von" # (en: 'not a superset of', MathPlayer: 'nicht Obermenge von', google: 'kein superset von') + - "⊆": # 0x2286 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist ein"] # (en: 'is a', google translation) + - T: "Teilmenge oder gleich" # (en: 'subset of or equal to', MathPlayer: 'Teilmenge von odre gleich', google: 'teilmenge von oder gleich') + - "⊇": # 0x2287 + - test: + if: "$Verbosity!='Terse'" + then: [T: "ist ein"] # (en: 'is a', google translation) + - T: "Obermenge oder gleich" # (en: 'superset of or equal to', MathPlayer: 'Obermenge von oder gleich', google: 'superset von oder gleich') - "⊈": # 0x2288 - test: if: "$Verbosity!='Terse'" @@ -976,7 +1197,7 @@ - "⊹": [T: "hermitesch konjugierte Matrix"] # 0x22b9 (en: 'hermitian conjugate matrix', MathPlayer: 'hermitian conjugate matrix', google: 'hermitische konjugierte matrix') - "⊺": [T: "einschalten"] # 0x22ba (en: 'intercalate', MathPlayer: 'intercalate', google: 'interkalieren') - "⊻": [T: "Ausschließendes Oder"] # 0x22bb (SRE: 'Ausschließendes Oder') - - "⊼": [T: "nand"] # 0x22bc (SRE: 'Nand verknüpft mit') + - "⊼": [T: "Nicht-und"] # 0x22bc (SRE: 'Nand verknüpft mit') - "⊽": [T: "Nor verknüpft mit"] # 0x22bd (en: 'nor', MathPlayer: 'nor', google: 'noch') - "⊾": [T: "rechter Winkel mit Bogen"] # 0x22be (en: 'right angle with arc', MathPlayer: 'right angle with arc', google: 'rechtwinkel mit bogen') - "⊿": [T: "rechtwinkliges Dreieck"] # 0x22bf (en: 'right triangle', MathPlayer: 'right triangle', google: 'rechtwinkliges dreieck') @@ -1849,7 +2070,7 @@ - "⧿": [T: "Miny"] # 0x29ff (en: 'miny', google: 'miny') - "⨀": [T: "Operator mit eingekreistem Punkt"] # 0x2a00 (en: 'circled dot operator', google: 'eingekreister punktbetreiber') - "⨁": [T: "Operator mit eingekreistem Plus"] # 0x2a01 (en: 'circled plus operator', google: 'kreis plus operator') - - "⨂": [T: "Operator mit eingekreistem Kreuz"] # 0x2a02 (en: 'circled times operator', google: 'eingekreistes times operator') + - "⨂": [T: "Operator mit eingekreistem Malzeichen"] # 0x2a02 (en: 'circled times operator', google: 'eingekreistes times operator') - "⨃": [T: "Vereinigungs-Operator mit Punkt"] # 0x2a03 (en: 'union operator with dot', google: 'gewerkschaftsbetreiber mit punkt') - "⨄": [T: "Vereinigungs-Operator mit Plus"] # 0x2a04 (en: 'union operator with plus', google: 'gewerkschaftsbetreiber mit plus') - "⨅": [T: "quadratischer Schnittmengen-Operator"] # 0x2a05 (en: 'square intersection operator', google: 'square intersection operator') @@ -1903,7 +2124,7 @@ - "⨵": [T: "Multiplikationszeichen im rechten Halbkreis"] # 0x2a35 (en: 'multiplication sign in right half circle', MathPlayer: 'multiplication sign in right half circle', google: 'multiplikationszeichen im rechten halbkreis') - "⨶": [T: "eingekreistes Multiplikationszeichen mit Circumflex-Akzent"] # 0x2a36 (en: 'circled multiplication sign with circumflex accent', MathPlayer: 'circled multiplication sign with circumflex accent', google: 'umkreistes multiplikationszeichen mit circumflex -akzent') - "⨷": [T: "Multiplikationszeichen im doppelten Kreis"] # 0x2a37 (en: 'multiplication sign in double circle', MathPlayer: 'multiplication sign in double circle', google: 'multiplikationszeichen im doppelkreis') - - "⨸": [T: "circled division sign"] # 0x2a38 + - "⨸": [T: "eingekreistes Divisionszeichen"] # 0x2a38 - "⨹": [T: "Pluszeichen im Dreieck"] # 0x2a39 (en: 'plus sign in triangle', MathPlayer: 'plus sign in triangle', google: 'plus melden sie sich im dreieck an') - "⨺": [T: "Minuszeichen im Dreieck"] # 0x2a3a (en: 'minus sign in triangle', MathPlayer: 'minus sign in triangle', google: 'minus zeichen im dreieck') - "⨻": [T: "Multiplikationszeichen im Dreieck"] # 0x2a3b (en: 'multiplication sign in triangle', MathPlayer: 'multiplication sign in triangle', google: 'multiplikationszeichen im dreieck') @@ -1930,8 +2151,8 @@ - "⩐": [T: "Closed Union mit Serifen und Smash-Produkten"] # 0x2a50 (en: 'closed union with serifs and smash product', MathPlayer: 'closed union with serifs and smash product', google: 'geschlossene vereinigung mit serifen und smash -produkt') - "⩑": [T: "Logisch und mit Punkt oben"] # 0x2a51 (en: 'logical and with dot above', google: 'logisch und mit punkt oben') - "⩒": [T: "Logisch oder mit Punkt oben"] # 0x2a52 (en: 'logical or with dot above', google: 'logisch oder mit punkt oben') - - "⩓": [T: "double logical and"] # 0x2a53 - - "⩔": [T: "double logical or"] # 0x2a54 + - "⩓": [T: "doppeltes logisches Und"] # 0x2a53 + - "⩔": [T: "doppeltes logisches Oder"] # 0x2a54 - "⩕": [T: "Zwei durchschneiden logisch und"] # 0x2a55 (en: 'two intersecting logical and', MathPlayer: 'two intersecting logical and', google: 'zwei kreuzende logische und') - "⩖": [T: "Zwei sich überschneidende logische Oder"] # 0x2a56 (en: 'two intersecting logical or', MathPlayer: 'two intersecting logical or', google: 'zwei kreuzende logische oder') - "⩗": [T: "Abfallend großes oder"] # 0x2a57 (en: 'sloping large or', MathPlayer: 'sloping large or', google: 'groß oder') @@ -1964,7 +2185,7 @@ - "⩲": [T: "Pluszeichen über Gleichheitszeichen"] # 0x2a72 (en: 'plus sign above equals sign', MathPlayer: 'equals with plus above', google: 'plus -zeichen oben gleiches zeichen') - "⩳": [T: "Gleichheitszeichen über Tilde-Operator"] # 0x2a73 (en: 'equals sign above tilde operator', MathPlayer: 'equals sign above tilde operator', google: 'gleicher vorzeichen über dem tilde -operator') - "⩴": [T: "Doppelpunkt gleich"] # 0x2a74 (en: 'double colon equal', MathPlayer: 'double colon equal', google: 'doppelter dickdarm gleich') - - "⩵": [T: "double equal"] # 0x2a75 (en: 'two consecutive equals signs') + - "⩵": [T: "doppeltes Gleichheitszeichen"] # 0x2a75 (en: 'two consecutive equals signs') - "⩶": [T: "Drei aufeinander folgende Gleichheitszeichen"] # 0x2a76 (en: 'three consecutive equals signs', google: 'drei aufeinanderfolgende anzeichen') - "⩷": [T: "Gleichheitszeichen mit zwei Punkten oben und zwei Punkten unten"] # 0x2a77 (en: 'equals sign with two dots above and two dots below', MathPlayer: 'equals sign with two dots above and two dots below', google: 'gleiches zeichen mit zwei punkten oben und zwei punkten unten') - "⩸": [T: "Gleichwertig mit vier Punkten oben"] # 0x2a78 (en: 'equivalent with four dots above', MathPlayer: 'equivalent with four dots above', google: 'äquivalent mit vier punkten oben') @@ -2058,10 +2279,10 @@ - "⫐": [T: "Superset geschlossen"] # 0x2ad0 (en: 'closed superset', MathPlayer: 'closed superset', google: 'geschlossener superset') - "⫑": [T: "Geschlossene Untermenge oder gleich"] # 0x2ad1 (en: 'closed subset or equal to', MathPlayer: 'closed subset or equal to', google: 'geschlossene untergruppe oder gleich') - "⫒": [T: "Superset geschlossen oder gleich"] # 0x2ad2 (en: 'closed superset or equal to', MathPlayer: 'closed superset or equal to', google: 'geschlossene superset oder gleich') - - "⫓": [T: "subset over superset"] # 0x2ad3 (en: 'subset above superset') - - "⫔": [T: "superset over subset"] # 0x2ad4 (en: 'superset above subset') - - "⫕": [T: "subset over subset"] # 0x2ad5 (en: 'subset above subset') - - "⫖": [T: "superset over superset"] # 0x2ad6 (en: 'superset above superset') + - "⫓": [T: "Teilmenge über Obermenge"] # 0x2ad3 (en: 'subset above superset') + - "⫔": [T: "Obermenge über Teilmenge"] # 0x2ad4 (en: 'superset above subset') + - "⫕": [T: "Teilmenge über Teilmenge"] # 0x2ad5 (en: 'subset above subset') + - "⫖": [T: "Obermenge über Obermenge"] # 0x2ad6 (en: 'superset above superset') - "⫗": [T: "Superset neben dem Subset"] # 0x2ad7 (en: 'superset beside subset', MathPlayer: 'superset beside subset', google: 'superset neben untergruppe') - "⫘": [T: "Superset Neben und Joined von Dash mit Subset"] # 0x2ad8 (en: 'superset beside and joined by dash with subset', MathPlayer: 'superset beside and joined by dash with subset', google: 'superset neben dem dash mit der untergruppe') - "⫙": [T: "Element der Öffnung nach unten"] # 0x2ad9 (en: 'element of opening downwards', MathPlayer: 'element of opening downwards', google: 'element der abwärts öffnen') @@ -2071,17 +2292,17 @@ - "⫝": [T: "Nicht Gabeln"] # 0x2add (en: 'nonforking', google: 'nicht -forking') - "⫞": [T: "Kurzer Linker Tack"] # 0x2ade (en: 'short left tack', google: 'kurzer linker tack') - "⫟": [T: "Kurzer Down Tack"] # 0x2adf (en: 'short down tack', google: 'kurzer tack') - - "⫠": [T: "Short Up Tack"] # 0x2ae0 (en: 'short up tack', google: 'kurzer tack') + - "⫠": [T: "kurzer Aufwärtshaken"] # 0x2ae0 (en: 'short up tack', google: 'kurzer tack') - "⫡": [T: "Rechtwinklig zu S"] # 0x2ae1 (en: 'perpendicular with s', google: 'senkrecht mit s') - "⫢": [T: "Vertikales Dreifach-Drehkreuz"] # 0x2ae2 (en: 'vertical bar triple right turnstile', google: 'vertikale bar dreifache rechte drehstil') - "⫣": [T: "Doppelter vertikaler Balken linkes Drehkreuz"] # 0x2ae3 (en: 'double vertical bar left turnstile', google: 'doppelte vertikale balken linke drehkreuze') - - "⫤": [T: "double left turnstile vertical bar"] # 0x2ae4 (en: 'vertical bar double left turnstile') + - "⫤": [T: "vertikaler Strich mit doppeltem linkem Drehkreuz"] # 0x2ae4 (en: 'vertical bar double left turnstile') - "⫥": [T: "Doppelter vertikaler Balken Doppelter linker Drehkreuz"] # 0x2ae5 (en: 'double vertical bar double left turnstile', google: 'doppelte vertikale balken doppelte linke drehkreuze') - "⫦": [T: "Long Dash von Left Member von Double Vertical"] # 0x2ae6 (en: 'long dash from left member of double vertical', MathPlayer: 'long dash from left member of double vertical', google: 'langer dash vom linken mitglied des doppel vertikalen') - - "⫧": [T: "Short Down Tack mit Overbar"] # 0x2ae7 (en: 'short down tack with overbar', MathPlayer: 'short down tack with overbar', google: 'kurzer tack mit overbar') - - "⫨": [T: "perpendicular over bar"] # 0x2ae8 (en: 'short up tack with underbar') - - "⫩": [T: "Short Up Tack Über Short Down Tack"] # 0x2ae9 (en: 'short up tack above short down tack', MathPlayer: 'short up tack above short down tack', google: 'kurzschluss über kurzer tack') - - "⫪": [T: "Double Down Tack"] # 0x2aea (en: 'double down tack', google: 'double down tack') + - "⫧": [T: "kurzer Abwärtshaken mit Überstrich"] # 0x2ae7 (en: 'short down tack with overbar', MathPlayer: 'short down tack with overbar', google: 'kurzer tack mit overbar') + - "⫨": [T: "kurzer Aufwärtshaken mit Unterstrich"] # 0x2ae8 (en: 'short up tack with underbar') + - "⫩": [T: "kurzer Aufwärtshaken über kurzem Abwärtshaken"] # 0x2ae9 (en: 'short up tack above short down tack', MathPlayer: 'short up tack above short down tack', google: 'kurzschluss über kurzer tack') + - "⫪": [T: "doppelter Abwärtshaken"] # 0x2aea (en: 'double down tack', google: 'double down tack') - "⫫": [T: "Verdoppeln Sie Tack"] # 0x2aeb (en: 'double up tack', MathPlayer: 'double up tack', google: 'doppelte tack') - "⫬": [T: "Doppelstrich nicht unterschreiben"] # 0x2aec (en: 'double stroke not sign', MathPlayer: 'double stroke not sign', google: 'doppelhub nicht unterschreiben') - "⫭": [T: "Umgekehrter Doppelstrich nicht unterschrieben"] # 0x2aed (en: 'reversed double stroke not sign', MathPlayer: 'reversed double stroke not sign', google: 'umgekehrter doppelstrich nicht unterschreiben') @@ -2313,29 +2534,29 @@ - "㋍": [T: "ergs"] # 0x32cd (google translation) - "㋎": [T: "elektronenvolt"] # 0x32ce (en: 'electron volts', google translation) - "㋏": [T: "haftungsschild beschränkt"] # 0x32cf (en: 'limited liability sign', google translation) - - "㍱": [T: "hectopascals"] # 0x3371 (google translation) - - "㍲": [T: "daltons"] # 0x3372 (google translation) + - "㍱": [T: "hektopascal"] # 0x3371 (google translation) + - "㍲": [T: "dalton"] # 0x3372 (google translation) - "㍳": [T: "astronomische einheiten"] # 0x3373 (en: 'astronomical units', google translation) - "㍴": [T: "riegel"] # 0x3374 (en: 'bars', google translation) - "㍵": [T: "o v"] # 0x3375 (google translation) - - "㍶": [T: "parsecs"] # 0x3376 (google translation) + - "㍶": [T: "parsec"] # 0x3376 (google translation) - "㍷": [T: "dezimeter"] # 0x3377 (en: 'decimeters', google translation) - - "㍸": [T: "dezimeter quadratisch"] # 0x3378 (en: 'decimeters squared', google translation) - - "㍹": [T: "dezimeter gewonnen"] # 0x3379 (en: 'decimeters cubed', google translation) - - "㍺": [T: "instrumentaleinheiten"] # 0x337a (en: 'instrumental units', google translation) - - "㎀": [T: "picoamps"] # 0x3380 (google translation) - - "㎁": [T: "nanoamps"] # 0x3381 (google translation) - - "㎂": [T: "mikroamps"] # 0x3382 (en: 'microamps', google translation) - - "㎃": [T: "milliamps"] # 0x3383 (google translation) - - "㎄": [T: "kiloamps"] # 0x3384 (google translation) - - "㎅": [T: "kilobytes"] # 0x3385 (google translation) + - "㍸": [T: "Quadratdezimeter"] # 0x3378 (en: 'decimeters squared', google translation) + - "㍹": [T: "Kubikdezimeter"] # 0x3379 (en: 'decimeters cubed', google translation) + - "㍺": [T: "internationale Einheit"] # 0x337a (en: 'instrumental units', google translation) + - "㎀": [T: "pikoampere"] # 0x3380 (google translation) + - "㎁": [T: "nanoampere"] # 0x3381 (google translation) + - "㎂": [T: "mikroampere"] # 0x3382 (en: 'microamps', google translation) + - "㎃": [T: "milliampere"] # 0x3383 (google translation) + - "㎄": [T: "kiloampere"] # 0x3384 (google translation) + - "㎅": [T: "kilobyte"] # 0x3385 (google translation) - "㎆": [T: "megabyte"] # 0x3386 (en: 'megabytes', google translation) - "㎇": [T: "gigabyte"] # 0x3387 (en: 'gigabytes', google translation) - "㎈": [T: "kalorien"] # 0x3388 (en: 'calories', google translation) - "㎉": [T: "kilokalorien"] # 0x3389 (en: 'kilocalories', google translation) - - "㎊": [T: "picofarads"] # 0x338a (google translation) - - "㎋": [T: "nanofarads"] # 0x338b (google translation) - - "㎌": [T: "mikrofaraden"] # 0x338c (en: 'microfarads', google translation) + - "㎊": [T: "pikofarad"] # 0x338a (google translation) + - "㎋": [T: "nanofarad"] # 0x338b (google translation) + - "㎌": [T: "mikrofarad"] # 0x338c (en: 'microfarads', google translation) - "㎍": [T: "mikrogramm"] # 0x338d (en: 'micrograms', google translation) - "㎎": [T: "milligramm"] # 0x338e (en: 'milligrams', google translation) - "㎏": [T: "kilogramm"] # 0x338f (en: 'kilograms', google translation) @@ -2390,9 +2611,9 @@ - "㏀": [T: "kilo-ohm"] # 0x33c0 (en: 'kilo-ohms', google translation) - "㏁": [T: "megaohm"] # 0x33c1 (en: 'megaohms', google translation) - "㏂": [T: "attometer"] # 0x33c2 (en: 'attometers', google translation) - - "㏃": [T: "becquerels"] # 0x33c3 (google translation) + - "㏃": [T: "becquerel"] # 0x33c3 (google translation) - "㏄": [T: "kubikzentimeter"] # 0x33c4 (en: 'cubic centimeters', google translation) - - "㏅": [T: "candelas"] # 0x33c5 (google translation) + - "㏅": [T: "candela"] # 0x33c5 (google translation) - "㏆": [T: "coulombs pro kilogramm"] # 0x33c6 (en: 'coulombs per kilogram', google translation) - "㏇": [T: "herzzeitvolumen"] # 0x33c7 (en: 'cardiac output', google translation) - "㏈": [T: "dezibel"] # 0x33c8 (en: 'decibels', google translation) @@ -2400,16 +2621,16 @@ - "㏊": [T: "hektar"] # 0x33ca (en: 'hectares', google translation) - "㏋": [T: "pferdestärke"] # 0x33cb (en: 'horsepower', google translation) - "㏌": [T: "zoll"] # 0x33cc (en: 'inches', google translation) - - "㏍": [T: "kilokelvins"] # 0x33cd (google translation) + - "㏍": [T: "kilokelvin"] # 0x33cd (google translation) - "㏎": [T: "kilometer"] # 0x33ce (en: 'kilometers', google translation) - "㏏": [T: "knoten"] # 0x33cf (en: 'knots', google translation) - "㏐": [T: "lumen"] # 0x33d0 (en: 'lumens', google translation) - - "㏑": [T: "natural log"] # 0x33d1 (google translation) + - "㏑": [T: "natürlicher Logarithmus"] # 0x33d1 (google translation) - "㏒": [T: "logarithmus"] # 0x33d2 (en: 'logarithm', google translation) - "㏓": [T: "lux"] # 0x33d3 (google translation) - - "㏔": [T: "millibarns"] # 0x33d4 (google translation) - - "㏕": [T: "mühlen"] # 0x33d5 (en: 'mills', google translation) - - "㏖": [T: "maulwürfe"] # 0x33d6 (en: 'moles', google translation) + - "㏔": [T: "millibar"] # 0x33d4 (google translation) + - "㏕": [T: "mil"] # 0x33d5 (en: 'mills', google translation) + - "㏖": [T: "mol"] # 0x33d6 (en: 'moles', google translation) - "㏗": [T: "p h"] # 0x33d7 (google translation) - "㏘": [T: "picometer"] # 0x33d8 (en: 'picometers', google translation) - "㏙": [T: "teile pro million"] # 0x33d9 (en: 'parts per million', google translation) @@ -2846,8 +3067,11 @@ - "": [T: "digamma"] # 0xed03 (google translation) - "ϝ": [T: "digamma"] # 0x3dd - "": [T: ""] # 0xed10 (en: 'd', google translation) + - "ⅆ": [T: "d kursiv mit Doppelstrich"] # 0x2146 (en: 'd', MathPlayer: 'd', google: 'ⅆ') - "": [T: ""] # 0xed11 (en: 'e', google translation) + - "ⅇ": [T: "e kursiv mit Doppelstrich"] # 0x2147 (en: 'e', MathPlayer: 'e', google: 'ⅇ') - "": [T: ""] # 0xed12 (en: 'i', google translation) + - "ⅈ": [T: "i kursiv mit Doppelstrich"] # 0x2148 (en: 'i', MathPlayer: 'i', google: 'ⅈ') - "": [T: ""] # 0xed13 (en: 'j', google translation) - "ⅅ": - spell: "translate('.', 'ⅅ', 'DD')" # 0xed16, 0x2145 @@ -2896,6 +3120,7 @@ - " ": [T: ""] # 0x205f - "": [T: ""] # 0xef04 - "": [T: ""] # 0xef05 + - " ": [T: ""] # 0x2003 - "": [T: ""] # 0xef06 - "": [T: ""] # 0xef07 - "": [T: ""] # 0xef08 @@ -2911,7 +3136,7 @@ - "": [T: "integral mit quadratisch"] # 0xef81 (en: 'integral with square', google translation) - "": [T: "integral mit schrägstrich"] # 0xef82 (en: 'integral with slash', google translation) - "": [T: "umgekehrter integral"] # 0xef83 (en: 'reversed integral', google translation) - - "": [T: "double null über double null"] # 0xef90 (en: 'double zero over double zero', google translation) + - "": [T: "doppelte Null über doppelter Null"] # 0xef90 (en: 'double zero over double zero', google translation) - "": [T: "null mit schrägstrich"] # 0xef91 (en: 'zero with slash', google translation) # fraktur chars in math alphabetic block and also MathType private use area diff --git a/Rules/Languages/de/unicode.yaml b/Rules/Languages/de/unicode.yaml index 142069a4c..bdd89fc76 100644 --- a/Rules/Languages/de/unicode.yaml +++ b/Rules/Languages/de/unicode.yaml @@ -320,9 +320,9 @@ else_test: if: "../../self::m:set or ../../../self::m:set" # inside a set then_test: - - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' - then: [T: "in"] # (google translation) - - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "in"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' then: [T: "element von"] # (en: 'member of', google translation) - else_if: $ClearSpeak_SetMemberSymbol = 'Element' then: [T: "element von"] # (en: 'element of', google translation) @@ -344,9 +344,9 @@ else_test: if: "../../self::m:set or ../../../self::m:set" # inside a set then_test: - - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' - then: [T: "nicht in"] # (en: 'not in', google translation) - - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "nicht in"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' then: [T: "nicht element von"] # (en: 'not member of', google translation) - else_if: $ClearSpeak_SetMemberSymbol = 'Element' then: [T: "nicht element von"] # (en: 'not element of', google translation) @@ -367,9 +367,9 @@ else_test: if: "../../self::m:set or ../../../self::m:set" # inside a set then_test: - - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' - then: [T: "in"] # (google translation) - - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "in"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' then: [T: "element von"] # (en: 'member of', google translation) - else_if: $ClearSpeak_SetMemberSymbol = 'Element' then: [T: "element von"] # (en: 'element of', google translation) diff --git a/Rules/Languages/en/SharedRules/default.yaml b/Rules/Languages/en/SharedRules/default.yaml index 21cfc256b..c1f07ec7d 100644 --- a/Rules/Languages/en/SharedRules/default.yaml +++ b/Rules/Languages/en/SharedRules/default.yaml @@ -62,7 +62,7 @@ - test: - if: "string-length(.) = 1 and text() != '_'" # need unicode.tdl to kick in for single letter tokens then: [x: "text()"] - - else_if: "@data-chem-element > 0 or @data-roman-numeral" # NavMode=Character needs this + - else_if: "@data-chem-element or @data-roman-numeral" # NavMode=Character needs this then: [spell: "text()", pause: "short"] else: [x: "translate(., '-_\u00A0', ' ')"] # from intent literals or from extra spaces added (which get deleted) @@ -237,26 +237,12 @@ - x: "*[2]" - t: "below" # phrase(x with z 'below' it) -# FIX: this only works for up to four dots (which is realistically all we need) -- name: dot-over - tag: modified-variable - match: "*[1][self::m:mi] and (*[2][translate(., '.', '')=''])" - replace: - - x: "*[1]" - - x: "IfThenElse(string-length(*[2])=1, 'dot', - IfThenElse(string-length(*[2])=2, 'double dot', - IfThenElse(string-length(*[2])=3, 'triple dot', 'quadruple dot') - ))" - - pause: short - -# FIX: the 'translate' list is a little different from 'modified-variable', so we also include 'mover' (unify?) - name: diacriticals - tag: [mover, modified-variable] + tag: mover match: "*[1][self::m:mi] and *[2][translate(., '\u0306\u030c.\u00A8\u02D9\u20DB\u20DC`^ˇ~→¯_', '')='']" replace: - x: "*[1]" - x: "*[2]" - - pause: short - name: default tag: mover @@ -436,23 +422,22 @@ - t: "end scripts" # phrase(At this point 'end scripts' occurs) - name: default - tag: [mtable, array] + tag: mtable variables: - IsColumnSilent: "false()" - - NumColumns: "CountTableColumns(.)" - - NumRows: "CountTableRows(.)" + - NumColumns: "count(*[1]/*) - IfThenElse(*/self::m:mlabeledtr, 1, 0)" match: "." replace: - t: "table with" # phrase(the 'table with' 3 rows) - - x: "$NumRows" + - x: count(*) - test: - if: "$NumRows=1" + if: count(*)=1 then: [t: "row"] # phrase(the table with 1 'row') else: [t: "rows"] # phrase(the table with 3 'rows') - t: "and" # phrase(the table with 3 rows 'and' 4 columns) - x: "$NumColumns" - test: - if: "$NumColumns=1" + if: "NumColumns=1" then: [t: "column"] # phrase(the table with 3 rows and 1 'column') else: [t: "columns"] # phrase(the table with 3 rows and 4 'columns') - pause: long diff --git a/Rules/Languages/en/unicode-full.yaml b/Rules/Languages/en/unicode-full.yaml index bdedfdcad..737625b21 100644 --- a/Rules/Languages/en/unicode-full.yaml +++ b/Rules/Languages/en/unicode-full.yaml @@ -10,6 +10,7 @@ - "¨": [t: "double dot"] # 0xa8 - "©": [t: "copyright"] # 0xa9 - "ª": [t: "feminine ordinal indicator"] # 0xaa + - "¬": [t: "not"] # 0xac - "«": [t: "left-pointing double angle quote mark"] # 0xab - "¯": # 0xaf - test: @@ -18,9 +19,24 @@ else: [t: "line"] - "²": [t: "two"] # 0xb2 - "³": [t: "three"] # 0xb3 + - "´": [t: "acute"] # 0xb4 - "µ": [t: "micro"] # 0xb5 - "¹": [t: "one"] # 0xb9 - "º": [t: "masculine ordinal indicator"] # 0xb9 + - "·": + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolDot = 'Auto'" + then: [t: "times"] + else: [t: "dot"] + - "×": # 0xd7 + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolX = 'Auto'" + then: [t: "times"] + else_test: + if: $ClearSpeak_MultSymbolX = 'By' + then: [t: "by"] + else: [t: "cross"] + - "÷": [t: "divided by"] # 0xf7 - "¡": [t: "inverted exclamation mark"] # 0xa1 - "¶": [t: "paragraph mark"] # 0xb6 - "¿": [t: "inverted question mark"] # 0xbf @@ -108,6 +124,14 @@ - "˽": [t: "modifier shelf"] # 0x2fd - "˾": [t: "modifier open shelf"] # 0x2fe - "˿": [t: "modifier low left arrow"] # 0x2ff + - "̀": [t: "grave accent embellishment"] # 0x300 + - "́": [t: "acute accent embellishment"] # 0x301 + - "̂": [t: "circumflex accent embellishment"] # 0x302 + - "̃": [t: "tilde embellishment"] # 0x303 + - "̄": [t: "macron embellishment"] # 0x304 + - "̅": [t: "overbar embellishment"] # 0x305 + - "̆": [t: "breve embellishment"] # 0x306 + - "̇": [t: "dot above embellishment"] # 0x307 - "̈": [t: "diaeresis embellishment"] # 0x308 - "̉": [t: "hook above embellishment"] # 0x309 - "̊": [t: "ring above embellishment"] # 0x30a @@ -197,9 +221,13 @@ - "ϒ": [t: "upsilon with hook"] # 0x3d2 - "ϓ": [t: "upsilon with acute and hook"] # 0x3d3 - "ϔ": [t: "upsilon with diaeresis and hook"] # 0x3d4 + - "ϕ": [t: "phi"] # 0x3d5 + - "ϖ": [t: "pi"] # 0x3d6 - "ϗ": [t: "kai"] # 0x3d7 - "Ϙ": [t: "cap archaic koppa"] # 0x3d8 - "ϙ": [t: "archaic koppa"] # 0x3d9 + - "ϵ": [t: "epsilon"] # 0x3f5 + - "϶": [t: "reversed epsilon"] # 0x3f6 - "А-Я": # 0x410 - 0x42f - test: if: "$CapitalLetters_Beep" @@ -271,6 +299,19 @@ then: [t: "dot"] else: [t: "bullet"] + - "…": # 0x2026 + test: + if: + - "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_Ellipses = 'Auto' or" + # must be ClearSpeak and $ClearSpeak_Ellipses = 'AndSoOn' + # speak '…' as 'and so on...' unless expr starts with '…' + - "../*[1][.='…']" + then: [t: "dot dot dot"] + else_test: # must have $ClearSpeak_Ellipses = 'AndSoOn' + if: "count(following-sibling::*) = 0" + then: [t: "and so on"] + else: [t: "and so on up to"] + - "‰": [t: "per mille"] # 0x2030 - "‱": [t: "per ten thousand"] # 0x2031 - "′": [t: "prime"] # 0x2032 @@ -427,6 +468,7 @@ - t: "script" - spell: "translate('.', 'ℐℒ℘ℬℰℱℳ', 'ILPBEFM')" + - "ℓ": [t: "script l"] # 0x2113 - "℔": [t: "pounds"] # 0x2114 - "№": [t: "number"] # 0x2116 - "℥": [t: "ounces"] # 0x2125 @@ -584,6 +626,7 @@ - "⇏": [t: "right double arrow with stroke"] # 0x21cf - "⇐": [t: "left double arrow"] # 0x21d0 - "⇑": [t: "up double arrow"] # 0x21d1 + - "⇒": [t: "implies"] # 0x21d2 - "⇓": [t: "down double arrow"] # 0x21d3 - "⇔": [t: "if and only if"] # 0x21d4 - "⇕": [t: "up down double arrow"] # 0x21d5 @@ -635,15 +678,81 @@ if: "$Verbosity!='Terse'" then: [ot: "the"] - t: "complement of" + - "∂": # 0x2202 + - test: + if: "$Verbosity='Terse'" + then: [t: "partial"] + else: [t: "partial derivative"] - "∃": [t: "there exists"] # 0x2203 - "∄": [t: "there does not exist"] # 0x2204 - "∅": [t: "empty set"] # 0x2205 + - "∆": # 0x2206 + - test: + if: "following-sibling::*" + then_test: + if: "$Verbosity!='Terse'" + then: [t: "the LahPlahsian of"] # "LahPlahsian" sounds better than "laplacian" in speech engines tested + else: [t: "LahPlahsian"] + else: [t: "LahPlahsian"] + - "∇": [t: "nahblah"] # 0x2207 + - "∉": # 0x2209 + # rule is identical to 0x2208 + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [t: "is not an element of"] + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [t: "not in"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [t: "not member of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [t: "not element of"] + - else: [t: "not belonging to"] # $ClearSpeak_SetMemberSymbol = 'Belongs' + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [t: "is not a member of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [t: "is not an element of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [t: "is not in"] + - else: [t: "does not belong to"] # $ClearSpeak_SetMemberSymbol = 'Belongs' + - "∊": # 0x220a + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: + - test: + if: "$Verbosity!='Terse' and not(parent::m:set)" # "the set x is an element of ..." sounds bad + then: [t: "is"] + - t: "an element of" + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [t: "in"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [t: "member of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [t: "element of"] + - else: [t: "belonging to"] # $ClearSpeak_SetMemberSymbol = 'Belongs' + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [t: "is a member of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [t: "is an element of"] + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [t: "is in"] + - else: [t: "belongs to"] # $ClearSpeak_SetMemberSymbol = 'Belongs' - "∋": [t: "contains the member"] # 0x220b - "∌": [t: "does not contain the member"] # 0x220c - "∍": [t: "contains the member"] # 0x220d - "∎": [t: "end of proof"] # 0x220e - "∏": [t: "product"] # 0x220f + - "∐": [t: "coproduct"] # 0x2210 - "∑": [t: "sum"] # 0x2211 - "−": [t: "minus"] # 0x2212 - "∓": [t: "minus or plus"] # 0x2213 @@ -658,6 +767,11 @@ then: [t: "dot"] else: [t: "times"] + - "√": # 0x221a + - test: + if: "$Verbosity!='Terse'" + then: [ot: "the"] + - t: "square root of" - "∛": # 0x221b - test: if: "$Verbosity!='Terse'" @@ -668,6 +782,11 @@ if: "$Verbosity!='Terse'" then: [ot: "the"] - t: "fourth root of" + - "∝": # 0x221d + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "proportional to" - "∞": [t: "infinity"] # 0x221e - "∟": [t: "right angle"] # 0x221f - "∠": [t: "angle"] # 0x2220 @@ -690,6 +809,11 @@ - "∳": [t: "anticlockwise contour integral"] # 0x2233 - "∴": [t: "therefore"] # 0x2234 - "∵": [t: "because"] # 0x2235 + - "∶": # 0x2236 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "to" - "∷": [t: "as"] # 0x2237 - "∸": [t: "dot minus"] # 0x2238 - "∹": [t: "has excess compared to"] # 0x2239 @@ -705,6 +829,11 @@ - t: "homothetic to" - "∼": [t: "varies with"] # 0x223c - "∽": [t: "reversed tilde"] # 0x223d + - "∾": # 0x223e + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "most positive" - "∿": [t: "sine wave"] # 0x223f - "≀": [t: "wreath product"] # 0x2240 - "≁": [t: "not tilde"] # 0x2241 @@ -806,6 +935,16 @@ then: [t: "is"] - t: "measured by" - "≟": [t: "has an unknown relationship with"] # 0x225f + - "≠": # 0x2260 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "not equal to" + - "≡": # 0x2261 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "identical to" - "≢": # 0x2262 - test: if: "$Verbosity!='Terse'" @@ -916,6 +1055,36 @@ - "≿": [t: "succeeds or is equivalent to"] # 0x227f - "⊀": [t: "does not precede"] # 0x2280 - "⊁": [t: "does not succeed"] # 0x2281 + - "⊂": # 0x2282 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is a"] + - t: "subset of" + - "⊃": # 0x2283 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is a"] + - t: "superset of" + - "⊄": # 0x2284 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "not a subset of" + - "⊅": # 0x2285 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is"] + - t: "not a superset of" + - "⊆": # 0x2286 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is a"] + - t: "subset of or equal to" + - "⊇": # 0x2287 + - test: + if: "$Verbosity!='Terse'" + then: [t: "is a"] + - t: "superset of or equal to" - "⊈": # 0x2288 - test: if: "$Verbosity!='Terse'" @@ -1234,7 +1403,7 @@ - "⏠": [t: "top tortoise shell bracket"] # 0x23e0 - "⏡": [t: "bottom tortoise shell bracket"] # 0x23e1 - "⏢": [t: "white trapezium"] # 0x23e2 - - "⏣": [t: "benzene ring with circle"] # 0x23e3 + - "⏣": [t: "benzene right with circle"] # 0x23e3 - "⏤": [t: "straightness"] # 0x23e4 - "⏥": [t: "flatness"] # 0x23e5 - "⏦": # 0x23e6 @@ -1286,7 +1455,7 @@ - t: "parenthesized" - spell: "translate('.', '⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵', 'abcdefghijklmnopqrstuvwxyz')" - - "Ⓐ-Ⓩ": # 0x24b6 - 0x24cf + - "Ⓐ-ⓩ": # 0x24b6 - 0x24cf - t: "circled" - spell: "translate('.', 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" @@ -1297,7 +1466,6 @@ - "ⓐ-ⓩ": # 0x24d0 - 0x24e9 - t: "circled" - spell: "translate('.', 'ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ', 'abcdefghijklmnopqrstuvwxyz')" - - "⓪": [t: "circled zero"] # 0x24ea - "⓫": [t: "black circled eleven"] # 0x24eb - "⓬": [t: "black circled twelve"] # 0x24ec @@ -1449,7 +1617,7 @@ - "⚆": [t: "white circle with dot right"] # 0x2686 - "⚇": [t: "white circle wiht two dots"] # 0x2687 - "⚈": [t: "black circle with dot right"] # 0x2688 - - "⚉": [t: "black circle with two dots"] # 0x2689 + - "⚉": [t: "black circle wiht two dots"] # 0x2689 - "⚪": [t: "medium white circle"] # 0x26aa - "⚫": [t: "medium black circle"] # 0x26ab - "⚬": [t: "medium small white circle"] # 0x26ac @@ -2879,8 +3047,11 @@ - "": [t: "digamma"] # 0xed03 - "ϝ": [t: "digamma"] # 0x3dd - "": [t: "d"] # 0xed10 + - "ⅆ": [t: "d"] # 0x2146 - "": [t: "e"] # 0xed11 + - "ⅇ": [t: "e"] # 0x2147 - "": [t: "i"] # 0xed12 + - "ⅈ": [t: "i"] # 0x2148 - "": [t: "j"] # 0xed13 - "ⅅ": - spell: "translate('.', 'ⅅ', 'DD')" # 0xed16, 0x2145 @@ -3495,6 +3666,12 @@ then: [t: "bold"] - spell: "translate('.', '𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗', '0123456789')" + - "-": # 0xf52e - 0xf537 (old MathType) + - test: + if: "not($IgnoreBold)" + then: [t: "bold"] + - spell: "translate('.', '', '0123456789')" + - "𝟬-𝟵": # 0x1D7EC - 0x1D7F5 - test: if: "not($IgnoreBold)" diff --git a/Rules/Languages/zh/tw/unicode-full.yaml b/Rules/Languages/zh/tw/unicode-full.yaml index 23a443fa6..9e4e85087 100644 --- a/Rules/Languages/zh/tw/unicode-full.yaml +++ b/Rules/Languages/zh/tw/unicode-full.yaml @@ -10,6 +10,7 @@ - "¨": [T: "double dot"] # 0xa8 (en: 'double dot', google translation) - "©": [T: "版權"] # 0xa9 (en: 'copyright', google translation) - "ª": [t: "feminine ordinal indicator"] # 0xaa (en: 'feminine ordinal indicator', google translation) + - "¬": [T: "非"] # 0xac (en: 'not', google translation) - "«": [t: "left-pointing double angle quote mark"] # 0xab (en: 'left-pointing double angle quote mark', google translation) - "¯": # 0xaf - test: @@ -18,9 +19,24 @@ else: [T: "橫線"] # (en: 'line') - "²": [T: "二"] # 0xb2 (en: 'two', google translation) - "³": [T: "三"] # 0xb3 (en: 'three', google translation) + - "´": [t: "acute"] # 0xb4 (en: 'acute', google translation) - "µ": [T: "微"] # 0xb5 (en: 'micro', google translation) - "¹": [T: "一"] # 0xb9 (en: 'one', google translation) - "º": [t: "masculine ordinal indicator"] # 0xb9 (en: 'masculine ordinal indicator', google translation) + - "·": + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolDot = 'Auto'" + then: [T: "乘"] # (en: 'times', google translation) + else: [T: "內積"] # (en: 'dot') + - "×": # 0xd7 + - test: + if: "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_MultSymbolX = 'Auto'" + then: [T: "乘"] # (en: 'times', google translation) + else_test: + if: $ClearSpeak_MultSymbolX = 'By' + then: [T: "乘"] # (en: 'by', google translation) + else: [T: "乘"] # (en: 'cross') + - "÷": [T: "除以"] # 0xf7 (en: 'divided by') - "ʰ": [t: "modifier small h"] # 0x2b0 (en: 'modifier small h', google translation) - "ʱ": [t: "modifier small h with hook"] # 0x2b1 (en: 'modifier small h with hook', google translation) - "ʲ": [t: "modifier small j"] # 0x2b2 (en: 'modifier small j', google translation) @@ -104,6 +120,14 @@ - "˽": [t: "modifier shelf"] # 0x2fd (en: 'modifier shelf', google translation) - "˾": [t: "modifier open shelf"] # 0x2fe (en: 'modifier open shelf', google translation) - "˿": [t: "modifier low left arrow"] # 0x2ff (en: 'modifier low left arrow', google translation) + - "̀": [t: "grave accent embellishment"] # 0x300 (en: 'grave accent embellishment', google translation) + - "́": [t: "acute accent embellishment"] # 0x301 (en: 'acute accent embellishment', google translation) + - "̂": [t: "circumflex accent embellishment"] # 0x302 (en: 'circumflex accent embellishment', google translation) + - "̃": [t: "tilde embellishment"] # 0x303 (en: 'tilde embellishment', google translation) + - "̄": [t: "macron embellishment"] # 0x304 (en: 'macron embellishment', google translation) + - "̅": [t: "overbar embellishment"] # 0x305 (en: 'overbar embellishment', google translation) + - "̆": [t: "breve embellishment"] # 0x306 (en: 'breve embellishment', google translation) + - "̇": [t: "dot above embellishment"] # 0x307 (en: 'dot above embellishment', google translation) - "̈": [t: "diaeresis embellishment"] # 0x308 (en: 'diaeresis embellishment', google translation) - "̉": [t: "hook above embellishment"] # 0x309 (en: 'hook above embellishment', google translation) - "̊": [t: "ring above embellishment"] # 0x30a (en: 'ring above embellishment', google translation) @@ -192,7 +216,11 @@ - "ϒ": [t: "upsilon with hook"] # 0x3d2 (en: 'upsilon with hook') - "ϓ": [t: "upsilon with acute and hook"] # 0x3d3 (en: 'upsilon with acute and hook') - "ϔ": [t: "upsilon with diaeresis and hook"] # 0x3d4 (en: 'upsilon with diaeresis and hook') + - "ϕ": [T: "phi"] # 0x3d5 (en: 'phi') + - "ϖ": [t: "pi"] # 0x3d6 (en: 'pi') - "ϗ": [t: "kai"] # 0x3d7 (en: 'kai') + - "ϵ": [T: "epsilon"] # 0x3f5 (en: 'epsilon') + - "϶": [t: "reversed epsilon"] # 0x3f6 (en: 'reversed epsilon') - "А-Я": # 0x410 - 0x42f - test: if: "$CapitalLetters_Beep" @@ -246,6 +274,9 @@ - "‐": [T: "連字符"] # 0x2010 (en: 'hyphen', google translation) - "‑": [T: "連字符"] # 0x2011 (en: 'hyphen', google translation) - "‒": [t: "圖破折號"] # 0x2012 (en: 'figure dash', google translation) + - "–": [t: "en dash"] # 0x2013 (google translation) + - "—": [t: "em dash"] # 0x2014 (google translation) + - "―": [T: "橫線"] # 0x2015 (en: 'horizontal bar', google translation) - "‖": [T: "雙垂直線"] # 0x2016 (en: 'double vertical line', google translation) - "†": [t: "dagger"] # 0x2020 (en: 'dagger', google translation) - "‡": [t: "double dagger"] # 0x2021 (en: 'double dagger', google translation) @@ -256,6 +287,19 @@ then: [T: "dot"] # (en: 'dot', google translation) else: [T: "bullet"] # (en: 'bullet', google translation) + - "…": # 0x2026 + test: + if: + - "$SpeechStyle != 'ClearSpeak' or $ClearSpeak_Ellipses = 'Auto' or" + # must be ClearSpeak and $ClearSpeak_Ellipses = 'AndSoOn' + # speak '…' as 'and so on...' unless expr starts with '…' + - "../*[1][.='…']" + then: [T: "點點點"] # (en: 'dot dot dot', google translation) + else_test: # must have $ClearSpeak_Ellipses = 'AndSoOn' + if: "count(following-sibling::*) = 0" + then: [T: "等等"] # (en: 'and so on', google translation) + else: [T: "等等"] # (en: 'and so on up to', google translation) + - "‰": [T: "千分"] # 0x2030 (en: 'per mille', google translation) - "‱": [T: "萬分"] # 0x2031 (en: 'per ten thousand', google translation) - "′": [T: "prime"] # 0x2032 @@ -402,6 +446,7 @@ - T: "草體" # (en: 'script', google translation) - SPELL: "translate('.', 'ℐℒ℘ℬℰℱℳ', 'ILPBEFM')" + - "ℓ": [T: "草體l"] # 0x2113 (en: 'script l', google translation) - "℔": [T: "磅"] # 0x2114 (en: 'pounds', google translation) - "№": [T: "數字"] # 0x2116 (en: 'number', google translation) - "℥": [T: "盎司"] # 0x2125 (en: 'ounces', google translation) @@ -486,6 +531,7 @@ - "↉": [T: "三分之0"] # 0x2189 (en: 'zero thirds', google translation) - "←": [T: "左箭頭"] # 0x2190 (en: 'leftwards arrow', google translation) - "↑": [T: "向上箭頭"] # 0x2191 (en: 'upwards arrow', google translation) + - "→": [T: "右箭頭"] # 0x2192 (en: 'rightwards arrow') - "↓": [T: "向下箭頭"] # 0x2193 (en: 'downwards arrow', google translation) - "↔": [T: "左右雙箭頭"] # 0x2194 (en: 'left right arrow') - "↕": [T: "上下雙箭頭"] # 0x2195 (en: 'up down arrow', google translation) @@ -559,6 +605,7 @@ - "⇏": [t: "帶撇向右粗箭頭"] # 0x21cf (en: 'rightwards double arrow with stroke', google translation) - "⇐": [t: "向左粗箭頭"] # 0x21d0 (en: 'leftwards double arrow', google translation) - "⇑": [t: "向上粗箭頭"] # 0x21d1 (en: 'upwards double arrow', google translation) + - "⇒": [t: "向右粗箭頭"] # 0x21d2 (en: 'rightwards double arrow', google translation) - "⇓": [t: "向下粗箭頭"] # 0x21d3 (en: 'downwards double arrow', google translation) - "⇔": [t: "左右粗箭頭"] # 0x21d4 (en: 'left right double arrow', google translation) - "⇕": [t: "上下粗箭頭"] # 0x21d5 (en: 'up down double arrow', google translation) @@ -604,19 +651,100 @@ - "⇽": [t: "向左開頭箭頭"] # 0x21fd (en: 'leftwards open headed arrow', google translation) - "⇾": [t: "向右開頭箭頭"] # 0x21fe (en: 'rightwards open headed arrow', google translation) - "⇿": [t: "左右開頭箭頭"] # 0x21ff (en: 'left right open headed arrow', google translation) + - "∀": [T: "對所有的"] # 0x2200 (en: 'for all') - "∁": # 0x2201 - test: if: "$Verbosity!='Terse'" then: [T: ""] # (en: 'the', google translation) - T: "補集" # (en: 'complement of') + - "∂": # 0x2202 + - test: + if: "$Verbosity='Terse'" + then: [T: "偏微分"] # (en: 'partial', google translation) + else: [T: "偏微分"] # (en: 'partial derivative') - "∃": [T: "存在"] # 0x2203 (en: 'there exists') - "∄": [T: "不存在"] # 0x2204 (en: 'there does not exist') - "∅": [T: "空集合"] # 0x2205 (en: 'empty set') + - "∆": # 0x2206 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'the', google translation) + - T: "變化量" # (en: 'laplacian of') - "∇": # 0x2207 - test: if: "$Verbosity!='Terse'" then: [T: ""] # (en: 'the', google translation) - T: "梯度" # (en: 'gradient of') + - "∈": # 0x2208 + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "屬於"] # (en: 'an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "在"] # (en: 'in', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "成員"] # (en: 'member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "元素"] # (en: 'element of', google translation) + - else: [T: "屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "是"] # (en: 'is a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "是一個元素"] # (en: 'is an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "在"] # (en: 'is in', google translation) + - else: [T: "屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belongs to') + - "∉": # 0x2209 + # rule is identical to 0x2208 + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "不屬於"] # (en: 'is not an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "不在"] # (en: 'not in', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "不是成員"] # (en: 'not member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "不是"] # (en: 'not element of', google translation) + - else: [T: "不屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'not belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "不是成員"] # (en: 'is not a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "不是一個元素"] # (en: 'is not an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "不在"] # (en: 'is not in', google translation) + - else: [T: "不屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'does not belong to') + - "∊": # 0x220a + - test: + if: "$SpeechStyle != 'ClearSpeak'" + then: [T: "屬於"] # (en: 'is an element of', google translation) + # Several options for speaking elements in ClearSpeak -- they split between being inside a set or not and then the option + else_test: + if: "../../self::m:set or ../../../self::m:set" # inside a set + then_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "在"] # (en: 'in', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "成員"] # (en: 'member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "元素"] # (en: 'element of', google translation) + - else: [T: "屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belonging to') + else_test: + - if: $ClearSpeak_SetMemberSymbol = 'Auto' or $ClearSpeak_SetMemberSymbol = 'Member' + then: [T: "是"] # (en: 'is a member of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'Element' + then: [T: "是一個元素"] # (en: 'is an element of', google translation) + - else_if: $ClearSpeak_SetMemberSymbol = 'In' + then: [T: "在"] # (en: 'is in', google translation) + - else: [T: "屬於"] # $ClearSpeak_SetMemberSymbol = 'Belongs' (en: 'belongs to') - "∋": [T: "包含"] # 0x220b (en: 'contains the member') - "∌": [T: "不包含"] # 0x220c (en: 'does not contain the member') - "∍": [T: "包含"] # 0x220d (en: 'contains the member') @@ -637,6 +765,11 @@ then: [T: "dot"] # (en: 'dot', google translation) else: [T: "乘"] # (en: 'times') + - "√": # 0x221a + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'the', google translation) + - T: "開平方根" # (en: 'square root of') - "∛": # 0x221b - test: if: "$Verbosity!='Terse'" @@ -647,6 +780,11 @@ if: "$Verbosity!='Terse'" then: [T: ""] # (en: 'the', google translation) - T: "開四次方根" # (en: 'fourth root of') + - "∝": # 0x221d + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "正比於" # (en: 'proportional to') - "∞": [T: "無限大"] # 0x221e (en: 'infinity') - "∟": [T: "直角"] # 0x221f (en: 'right angle') - "∠": [T: "角"] # 0x2220 (en: 'angle') @@ -654,6 +792,8 @@ - "∢": [T: "球面角"] # 0x2222 (en: 'spherical angle') - "∣": [T: "整除"] # 0x2223 (en: 'divides') - "∤": [T: "不整除"] # 0x2224 (en: 'does not divide') + - "∧": [T: "邏輯與"] # 0x2227 (en: 'and') + - "∨": [T: "邏輯或"] # 0x2228 (en: 'or') - "∩": [T: "交集"] # 0x2229 (en: 'intersection') - "∪": [T: "聯集"] # 0x222a (en: 'union') - "∫": [T: "積分"] # 0x222b (en: 'integral') @@ -667,6 +807,11 @@ - "∳": [T: "逆時針輪廓積分"] # 0x2233 (en: 'anticlockwise contour integral') - "∴": [T: "所以"] # 0x2234 (en: 'therefore') - "∵": [T: "因為"] # 0x2235 (en: 'because') + - "∶": # 0x2236 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "比" # (en: 'to') - "∷": [T: "當成"] # 0x2237 (en: 'as') - "∸": [T: "點負號"] # 0x2238 (en: 'dot minus') - "∹": [T: "超出"] # 0x2239 (en: 'has excess compared to') @@ -682,6 +827,11 @@ - T: "同位" # (en: 'homothetic to') - "∼": [T: "波浪符"] # 0x223c (en: 'varies with') - "∽": [T: "反波浪符"] # 0x223d (en: 'reversed tilde') + - "∾": # 0x223e + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "正無限大" # (en: 'most positive') - "∿": [T: "正弦波型"] # 0x223f (en: 'sine wave') - "≀": [t: "wreath product"] # 0x2240 (en: 'wreath product') - "≁": [t: "not tilde"] # 0x2241 (en: 'not tilde') @@ -783,6 +933,16 @@ then: [T: ""] # (en: 'is', google translation) - T: "測量" # (en: 'measured by') - "≟": [T: "問號等於"] # 0x225f (en: 'has an unknown relationship with') + - "≠": # 0x2260 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "不等於" # (en: 'not equal to') + - "≡": # 0x2261 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "恆等於" # (en: 'identical to') - "≢": # 0x2262 - test: if: "$Verbosity!='Terse'" @@ -793,6 +953,8 @@ if: "$Verbosity!='Terse'" then: [T: ""] # (en: 'is', google translation) - T: "完全等於" # (en: 'strictly equivalent to') + - "≦": [T: "小於等於"] # 0x2266 (en: 'less than over equal to') + - "≧": [T: "大於等於"] # 0x2267 (en: 'greater than over equal to') - "≨": # 0x2268 - test: if: "$Verbosity!='Terse'" @@ -891,6 +1053,36 @@ - "≿": [T: "後於或等價於"] # 0x227f (en: 'succeeds or is equivalent to') - "⊀": [T: "不先於"] # 0x2280 (en: 'does not precede') - "⊁": [T: "不後於"] # 0x2281 (en: 'does not succeed') + - "⊂": # 0x2282 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is a', google translation) + - T: "包含於" # (en: 'subset of') + - "⊃": # 0x2283 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is a', google translation) + - T: "包含" # (en: 'superset of') + - "⊄": # 0x2284 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "不包含於" # (en: 'not a subset of') + - "⊅": # 0x2285 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is', google translation) + - T: "不包含" # (en: 'not a superset of') + - "⊆": # 0x2286 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is a', google translation) + - T: "包含於或等於" # (en: 'subset of or equal to') + - "⊇": # 0x2287 + - test: + if: "$Verbosity!='Terse'" + then: [T: ""] # (en: 'is a', google translation) + - T: "包含或等於" # (en: 'superset of or equal to') - "⊈": # 0x2288 - test: if: "$Verbosity!='Terse'" @@ -2791,8 +2983,11 @@ - "": [t: "digamma"] # 0xed03 (google translation) - "ϝ": [t: "Diggmma"] # 0x3dd (en: 'digamma') - "": [T: "d"] # 0xed10 (en: 'd', google translation) + - "ⅆ": [T: "d"] # 0x2146 (en: 'd', google translation) - "": [T: "e"] # 0xed11 (en: 'e', google translation) + - "ⅇ": [T: "e"] # 0x2147 (en: 'e', google translation) - "": [T: "i"] # 0xed12 (en: 'i', google translation) + - "ⅈ": [T: "i"] # 0x2148 (en: 'i', google translation) - "": [T: "j"] # 0xed13 (en: 'j', google translation) - "ⅅ": - SPELL: "translate('.', 'ⅅ', 'DD')" # 0xed16, 0x2145 diff --git a/build.rs b/build.rs index b8be2e9a9..a48b26033 100644 --- a/build.rs +++ b/build.rs @@ -151,6 +151,9 @@ fn zip_entry( /// Zip up each language and braille dir /// Note: regional variations (including zh-cn and zh-tw) are zipped together into one zip file fn main() { + println!("cargo::rerun-if-changed=build.rs"); + println!("cargo::rerun-if-changed=Rules"); + // This doesn't work because the build claims OUT_DIR is not defined(?) // let archive = PathBuf::from(concat!(env!("OUT_DIR"),"/rules.zip")); if std::env::var("CARGO_FEATURE_INCLUDE_ZIP").is_ok() { @@ -198,10 +201,5 @@ fn main() { if let Err(e) = archive_zip.finish() { panic!("Error in zip.finish(): {}", e); } - println!("cargo::rerun-if-changed=Rules"); - } else { - // Without include-zip, Rules are loaded at runtime from disk and must not - // re-run this script (or slow the build) on every yaml edit. - println!("cargo::rerun-if-changed=build.rs"); } } diff --git a/docs/index.md b/docs/index.md index 64b82251d..f0e944723 100644 --- a/docs/index.md +++ b/docs/index.md @@ -175,7 +175,7 @@ Translators: * German -- Nazli Andjic, Robert Graf and Paul Libbrecht (IU International University of Applied Sciences) * Indonesian -- Dr. Pinta Deniyanti Sampoerno, M.Si; Dr. Meiliasari, S.Pd., M.Sc; and Ari Hendarno, S.Pd., M.Kom. * Norwegian -- Marthe Gjelstad, National Library of Norway, Kvile -* Russian -- Danil Kostenkov +* Russian (both speech and braille) -- Danil Kostenkov * Spanish -- Noelia Ruiz Martínez (also helped with NVDA add-on development) and María Allo Roldán * Swedish -- Tim Arborealis Lötberg, Swedish Agency for Accessible Media (MTM) and Anders Eklund, SPSM * Vietnamese -- Dang Hoai Phúc and Trang Pham diff --git a/docs/ru/User_guide_for_MathCAT_ru.md b/docs/ru/User_guide_for_MathCAT_ru.md index fec001990..3214c89ac 100644 --- a/docs/ru/User_guide_for_MathCAT_ru.md +++ b/docs/ru/User_guide_for_MathCAT_ru.md @@ -40,7 +40,7 @@ MathCAT можно настроить в соответствии с вашим - Особые потребности: - \[Незрячие.\] Однозначное озвучивание. - Слабовидящие. Более краткое озвучивание. - - Трудности в обучении. Более краткое озвучивание. + - Когнитивные нарушения. Более краткое озвучивание. - Язык: (по умолчанию используется язык программы экранного доступа) - \[Английский (en)\] - Вьетнамский (vi) diff --git a/docs/ru/helpers.md b/docs/ru/helpers.md index c273404ea..be36a6116 100644 --- a/docs/ru/helpers.md +++ b/docs/ru/helpers.md @@ -133,7 +133,7 @@ fn common_fraction_half() { ``` Для русского языка строка `test` будет выглядеть так: ``` - test("ru", "SimpleSpeak", expr, "одна вторая"); + test("fr", "SimpleSpeak", expr, "одна вторая"); ``` После перевода нескольких тестов запустите автоматическую проверку. diff --git a/src/braille.rs b/src/braille.rs index 9aa7a92a3..8def4c87e 100644 --- a/src/braille.rs +++ b/src/braille.rs @@ -895,6 +895,7 @@ static UEB_INDICATOR_REPLACEMENTS: phf::Map<&str, &str> = phf_map! { "1" => "⠰", // Grade 1 symbol "𝟙" => "⠰⠰", // Grade 1 word "L" => "", // Letter left in to assist in locating letters + "A" => "", // Letter accent indicator -- should be followed by accent cells and then "L" for char "D" => "XXX", // German (Deutsche) -- from prefs "G" => "⠨", // Greek "V" => "⠨⠈", // Greek Variants @@ -933,6 +934,24 @@ fn is_letter_number(ch: char) -> bool { matches!(ch, '⠁' | '⠃' | '⠉' | '⠙' | '⠑' | '⠋' | '⠛' | '⠓' | '⠊' | '⠚') } +fn is_braille_char(ch: char) -> bool { + ('\u{2800}'..='\u{28ff}').contains(&ch) +} + +/// After an 'A' accent indicator, skip accent braille cells and return the index of the following 'L'. +/// Returns None if the accented-letter pattern is malformed. +fn index_after_accent_to_l(chars: &[char], after_a: usize) -> Option { + let mut j = after_a; + while j < chars.len() && is_braille_char(chars[j]) { + j += 1; + } + if j < chars.len() && chars[j] == 'L' { + Some(j) + } else { + None + } +} + static SHORT_FORMS: phf::Set<&str> = phf_set! { "L⠁L⠃", "L⠁L⠃L⠧", "L⠁L⠉", "L⠁L⠉L⠗", "L⠁L⠋", "L⠁L⠋L⠝", "L⠁L⠋L⠺", "L⠁L⠛", "L⠁L⠛L⠌", "L⠁L⠇", @@ -950,7 +969,7 @@ static SHORT_FORMS: phf::Set<&str> = phf_set! { }; fn is_letter_prefix(ch: char) -> bool { - matches!(ch, 'B' | 'I' | '𝔹' | 'S' | 'T' | 'D' | 'C' | '𝐶' | '𝑐') + matches!(ch, 'B' | 'I' | '𝔹' | 'S' | 'T' | 'D' | 'C' | '𝐶' | '𝑐' | 'A') } // Trim braille spaces before and after braille indicators @@ -958,7 +977,7 @@ fn is_letter_prefix(ch: char) -> bool { // Note: fraction over is not listed due to example 42(4) which shows a space before the "/" // static ref REMOVE_SPACE_BEFORE_BRAILLE_INDICATORS: Regex = // Regex::new(r"(⠄⠄⠄|⠤⠤⠤)W+([⠼⠸⠪])").unwrap(); -static REPLACE_INDICATORS: LazyLock = LazyLock::new(|| Regex::new(r"([1𝟙SB𝔹TIREDGVHP𝐶𝑐CLMNW𝐖swe,.-—―#ocb])").unwrap()); +static REPLACE_INDICATORS: LazyLock = LazyLock::new(|| Regex::new(r"([1𝟙SB𝔹TIREDGVHP𝐶𝑐CLAMNW𝐖swe,.-—―#ocb])").unwrap()); static COLLAPSE_SPACES: LazyLock = LazyLock::new(|| Regex::new(r"⠀⠀+").unwrap()); /// Transcriber-defined typeforms pulled from prefs (their braille is configurable), used by the @@ -1340,6 +1359,16 @@ fn capitals_to_word_mode(braille: &str) -> String { // Greek letters are a bit exceptional in that the pattern is "CGLx" -- bump 'i' next_non_cap += 1; } + if chars[next_non_cap] == 'A' { + // Accented capital: CA...Lx + match index_after_accent_to_l(&chars, next_non_cap + 1) { + Some(i_l) => next_non_cap = i_l, + None => { + error!("capitals_to_word_mode: internal error: didn't find L after CA in '{}'.", + chars[i..].iter().take(8).collect::()); + } + } + } if chars[next_non_cap] != 'L' { error!("capitals_to_word_mode: internal error: didn't find L after C in '{}'.", chars[i..next_non_cap+2].iter().collect::().as_str()); @@ -1347,6 +1376,25 @@ fn capitals_to_word_mode(braille: &str) -> String { let i_braille_char = next_non_cap + 2; result.push_str(String::from_iter(&chars[i..i_braille_char]).as_str()); i = i_braille_char; + } else if ch == 'A' { + // Lowercase accented letter: A...Lx + if is_word_mode { + result.push('e'); + is_word_mode = false; + } + match index_after_accent_to_l(&chars, i + 1) { + Some(i_l) => { + let i_braille_char = i_l + 2; + result.push_str(String::from_iter(&chars[i..i_braille_char]).as_str()); + i = i_braille_char; + } + None => { + error!("capitals_to_word_mode: internal error: didn't find L after A in '{}'.", + chars[i..].iter().take(8).collect::()); + result.push(ch); + i += 1; + } + } } else if ch == 'L' { // must be lowercase -- uppercase consumed above // assert!(LETTERS.contains(&unhighlight(chars[i+1]))); not true for other alphabets if is_word_mode { @@ -1561,15 +1609,23 @@ fn remove_unneeded_mode_changes(raw_braille: &str, start_mode: UEB_Mode, start_d match ch { 'L' => { // note: be aware of '#' case for Numeric because '1' might already be generated - // let prev_ch = if i > 1 {chars[i-1]} else {'1'}; // '1' -- anything beside ',' or '.' - // if duration == UEB_Duration::Symbol || - // ( ",. ".contains(prev_ch) && LETTER_NUMBERS.contains(&unhighlight(chars[i+1])) ) { - // result.push('1'); // need to retain grade 1 indicator (RUEB 6.5.2) - // } // let the default case handle pushing on the chars for the letter result.push(ch); i += 1; }, + 'A' => { + // Accented letter: keep A...Lx together so accent cells don't end G1 symbol mode + match index_after_accent_to_l(&chars, i + 1) { + Some(i_l) => { + result.extend(chars[i..i_l + 2].iter().copied()); // A + accents + L + letter + i = i_l + 2; + } + None => { + result.push(ch); + i += 1; + } + } + }, '1' | '𝟙' => { assert!(ch == '1' || duration != UEB_Duration::Symbol); // if '𝟙', should be Word or Passage duration // nothing to do -- let the default case handle the following chars @@ -1628,14 +1684,29 @@ fn remove_unneeded_mode_changes(raw_braille: &str, start_mode: UEB_Mode, start_d i += right_matched_chars.len(); }, 'C' => { - // Want 'C' before 'L'; Could be CC for word cap -- if so, eat it and move on + // Want 'C' before 'L' (or 'CA...L' / 'CGL'). Could be CC for word cap -- if so, eat it and move on // Note: guaranteed that there is a char after the 'C', so chars[i+1] is safe if chars[i+1] == 'C' { cap_word_mode = true; i += 1; } else { - let is_greek = chars[i+1] == 'G'; - let (is_alone, right_matched_chars, n_letters) = stands_alone(&chars, if is_greek {i+2} else {i+1}); + let mut letter_idx = i + 1; + let is_greek = chars[letter_idx] == 'G'; + if is_greek { + letter_idx += 1; + } + if letter_idx < chars.len() && chars[letter_idx] == 'A' { + match index_after_accent_to_l(&chars, letter_idx + 1) { + Some(i_l) => letter_idx = i_l, + None => { + error!("Internal error: 'CA' without L at index={i} in '{raw_braille}'"); + result.push('C'); + i += 1; + continue; + } + } + } + let (is_alone, right_matched_chars, n_letters) = stands_alone(&chars, letter_idx); // GTM 1.2.1 says we only need to use G1 for single letters or sequences that are a shortform (e.g, "ab") if is_alone && (n_letters == 1 || is_short_form(&right_matched_chars[..2*n_letters])) { // debug!(" is_alone -- pushing '1'"); @@ -1648,12 +1719,33 @@ fn remove_unneeded_mode_changes(raw_braille: &str, start_mode: UEB_Mode, start_d result.push('C'); if is_greek { result.push('G'); - i += 1; } start_g2_letter = Some(i); - // debug!(" pushing 'C' + {:?}", right_matched_chars); + // right_matched starts at A or L (not including C/G) right_matched_chars.iter().for_each(|&ch| result.push(ch)); - i += 1 + right_matched_chars.len(); + i += 1 + if is_greek {1} else {0} + right_matched_chars.len(); + } + }, + 'A' => { + // Accented lowercase letter: A...Lx + match index_after_accent_to_l(&chars, i + 1) { + Some(i_l) => { + if start_g2_letter.is_none() { + start_g2_letter = Some(i); + } + let (is_alone, right_matched_chars, n_letters) = stands_alone(&chars, i_l); + if is_alone && (n_letters == 1 || is_short_form(&right_matched_chars[..2*n_letters])) { + result.push('1'); + mode = UEB_Mode::Grade1; + } + right_matched_chars.iter().for_each(|&ch| result.push(ch)); + i += right_matched_chars.len(); + } + None => { + error!("Internal error: 'A' without L at index={i} in '{raw_braille}'"); + result.push(ch); + i += 1; + } } }, '1' => { @@ -1746,8 +1838,21 @@ fn stands_alone(chars: &[char], i: usize) -> (bool, &[char], usize) { // we scan forward and check the conditions for "standing-alone" assert_eq!(chars[i], 'L', "'stands_alone' starts with non 'L'"); // debug!("stands_alone: i={}, chars: {:?}", i, chars); - if !left_side_stands_alone(&chars[0..i]) { - return (false, &chars[i..i+2], 0); + // Exclude accent prefix A... immediately before this L (part of the same letter) + let mut letter_start = i; + let mut left_end = i; + if left_end > 0 { + let mut j = left_end; + while j > 0 && is_braille_char(chars[j - 1]) { + j -= 1; + } + if j > 0 && chars[j - 1] == 'A' { + letter_start = j - 1; + left_end = j - 1; + } + } + if !left_side_stands_alone(&chars[0..left_end]) { + return (false, &chars[letter_start..i+2], 0); } let (mut is_alone, n_letters, n_right_matched) = right_side_stands_alone(&chars[i+2..]); @@ -1759,7 +1864,8 @@ fn stands_alone(chars: &[char], i: usize) -> (bool, &[char], usize) { is_alone = false; } } - return (is_alone, &chars[i..i+2+n_right_matched], n_letters); + // Include any A... prefix so callers emit the full accented letter + return (is_alone, &chars[letter_start..i+2+n_right_matched], n_letters); /// chars before 'L' fn left_side_stands_alone(chars: &[char]) -> bool { @@ -1803,6 +1909,17 @@ fn stands_alone(chars: &[char], i: usize) -> (bool, &[char], usize) { if !intervening_chars_mode && ch == 'L' { n_letters += 1; i += 1; // ignore 'Lx' and also ignore 'ox' + } else if !intervening_chars_mode && ch == 'A' { + // Accented letter A...Lx counts as one letter + match index_after_accent_to_l(chars, i + 1) { + Some(i_l) => { + n_letters += 1; + i = i_l + 1; // now at letter braille; loop end advances past it + } + None => { + return (false, n_letters, i); + } + } } else if ch == 'c' || ch == 'b' { i += 1; // ignore 'Lx' and also ignore 'ox' } else if is_right_intervening_char(ch) { diff --git a/src/canonicalize.rs b/src/canonicalize.rs index 380c4de0c..a93ce3b44 100644 --- a/src/canonicalize.rs +++ b/src/canonicalize.rs @@ -5535,10 +5535,9 @@ mod canonicalize_tests { } #[test] - #[ignore] // need to figure out a test for this ("|" should have a precedence around ":" since that is an alternative notation for "such that", but "∣" is higher precedence) fn vertical_bar_divides() -> Result<()> { let test_str = " - x+y |12 + x+y |12 "; let target_str = " @@ -5547,7 +5546,7 @@ mod canonicalize_tests { + y - + | 12 "; @@ -6912,7 +6911,7 @@ mod canonicalize_tests { j k - l + l "; diff --git a/src/chemistry.rs b/src/chemistry.rs index 64656dca8..5f4375df3 100644 --- a/src/chemistry.rs +++ b/src/chemistry.rs @@ -2146,7 +2146,6 @@ mod chem_tests { } #[test] - #[ignore] // It would be good to say "not chemistry" for this, but there aren't rules for that at the moment fn test_water_bond() -> Result<()> { return chem_test(|| { let test11 = r#"H2O=O"#; diff --git a/src/interface.rs b/src/interface.rs index 317d7ce28..502a31f3a 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -1386,11 +1386,11 @@ mod tests { set_mathml(good_mathml)?; let bad_mathml = "&xabc;"; assert!(set_mathml(bad_mathml).is_err()); - assert!(get_spoken_text()? == ""); + assert!(get_spoken_text()?.is_empty()); set_mathml(good_mathml)?; let bad_mathml = "garbage"; assert!(set_mathml(bad_mathml).is_err()); - assert!(get_spoken_text()? == ""); + assert!(get_spoken_text()?.is_empty()); return Ok( () ); }); } @@ -1417,7 +1417,7 @@ mod tests { x-y "; - set_mathml(&expr)?; + set_mathml(expr)?; let speech = get_spoken_text()?; // Rule-generated SSML must pass through verbatim (not XML-entity-encoded). assert!(!speech.contains("<")); diff --git a/src/navigate.rs b/src/navigate.rs index 6fb170d52..4aa65b33b 100644 --- a/src/navigate.rs +++ b/src/navigate.rs @@ -1009,10 +1009,10 @@ mod tests { match do_navigate_command_string(mathml, command) { Err(e) => { panic!("\nStarting at '{}', '{} failed.\n{}", - start_id, command, &crate::interface::errors_to_string(&e)) + start_id, command, crate::interface::errors_to_string(&e)) }, Ok(nav_speech) => { - let nav_speech = nav_speech.trim_end_matches(&[' ', ',', ';']); + let nav_speech = nav_speech.trim_end_matches([' ', ',', ';']); // debug!("Full speech: {}", nav_speech); if !result_id.is_empty() { let (id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml); diff --git a/src/prefs.rs b/src/prefs.rs index d36cde237..f51fd91fe 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -930,6 +930,54 @@ mod tests { return stripped_path } + struct TestRulesDir { + path: PathBuf, + } + + impl TestRulesDir { + fn path(&self) -> &Path { + return &self.path; + } + } + + impl Drop for TestRulesDir { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.path); + } + } + + fn copy_dir_recursive(from: &Path, to: &Path) { + std::fs::create_dir_all(to).unwrap(); + for entry in std::fs::read_dir(from).unwrap() { + let entry = entry.unwrap(); + let file_type = entry.file_type().unwrap(); + let target = to.join(entry.file_name()); + if file_type.is_dir() { + copy_dir_recursive(&entry.path(), &target); + } else { + std::fs::copy(entry.path(), target).unwrap(); + } + } + } + + fn test_rules_dir_with_prefs() -> TestRulesDir { + let path = std::env::temp_dir().join(format!("mathcat-prefs-test-{}", fastrand::u64(..))); + std::fs::create_dir_all(&path).unwrap(); + + let source_rules = abs_rules_dir_path(); + let prefs = std::fs::read_to_string(source_rules.join("prefs.yaml")).unwrap() + .replace("Language: Auto", "Language: zz"); + std::fs::write(path.join("prefs.yaml"), prefs).unwrap(); + std::fs::copy(source_rules.join("intent.yaml"), path.join("intent.yaml")).unwrap(); + std::fs::copy(source_rules.join("definitions.yaml"), path.join("definitions.yaml")).unwrap(); + + copy_dir_recursive(&source_rules.join("Languages").join("en"), &path.join("Languages").join("en")); + copy_dir_recursive(&source_rules.join("Languages").join("zz"), &path.join("Languages").join("zz")); + copy_dir_recursive(&source_rules.join("Braille").join("Nemeth"), &path.join("Braille").join("Nemeth")); + + return TestRulesDir { path }; + } + fn speech_rule_files_cache_is_empty() -> bool { crate::speech::SPEECH_RULES.with(|rules| rules.borrow().rule_files_cache_is_empty()) } @@ -1230,124 +1278,36 @@ cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] { }); } - /// #262: MathCAT must notice when a rule file on disk changes and reload it. - /// This copies the rule files it needs (`en`, `zz`, and `Nemeth`) into a uniquely-named - /// temp dir, so the test can freely modify files and run in parallel without touching the - /// real Rules dir or the user's prefs file. All global state is restored on the way out. #[test] fn test_up_to_date() { use std::fs; use std::thread::sleep; use std::time::Duration; - use crate::interface; - use tempfile::Builder; - - /// Recursively copy `src` into `dst` (creating `dst`). - fn copy_dir_all(src: &Path, dst: &Path) { - fs::create_dir_all(dst).unwrap(); - for entry in fs::read_dir(src).unwrap() { - let entry = entry.unwrap(); - let from = entry.path(); - let to = dst.join(entry.file_name()); - if from.is_dir() { - copy_dir_all(&from, &to); - } else { - fs::copy(&from, &to).unwrap(); - } - } - } - - // Restore all thread-local global state (rules dir, prefs, caches) when the test exits, - // even on a panic, so other tests sharing this thread are unaffected. - struct RestoreGuard; - impl Drop for RestoreGuard { - fn drop(&mut self) { - let _ = interface::set_preference("CheckRuleFiles", "Prefs"); - PREF_MANAGER.with(|pref_manager| { - let mut pref_manager = pref_manager.borrow_mut(); - // Force a full re-read: the stale temp paths would otherwise fool the up-to-date check. - pref_manager.sys_prefs_file = None; - pref_manager.user_prefs_file = None; - let _ = pref_manager.initialize(abs_rules_dir_path()); - }); - } - } - - let temp_dir = Builder::new() - .prefix("mathcat-up-to-date-") - .tempdir() - .expect("create temp rules dir"); - let temp_rules_dir = temp_dir.path(); - let real_rules_dir = abs_rules_dir_path(); - - // Only the "en" (fallback), "zz" (test language), and "Nemeth" (braille) rules are needed, - // plus the top-level files they include (incl. the full system prefs.yaml — partial YAML - // left keys like DecimalSeparator missing and crashed on CI with no user AppData prefs). - copy_dir_all(&real_rules_dir.join("Languages/en"), &temp_rules_dir.join("Languages/en")); - copy_dir_all(&real_rules_dir.join("Languages/zz"), &temp_rules_dir.join("Languages/zz")); - copy_dir_all(&real_rules_dir.join("Intent"), &temp_rules_dir.join("Intent")); - copy_dir_all(&real_rules_dir.join("Braille/Nemeth"), &temp_rules_dir.join("Braille/Nemeth")); - fs::copy(real_rules_dir.join("definitions.yaml"), temp_rules_dir.join("definitions.yaml")).unwrap(); - fs::copy(real_rules_dir.join("intent.yaml"), temp_rules_dir.join("intent.yaml")).unwrap(); - fs::copy(real_rules_dir.join("Braille/definitions.yaml"), temp_rules_dir.join("Braille/definitions.yaml")).unwrap(); - fs::copy(real_rules_dir.join("prefs.yaml"), temp_rules_dir.join("prefs.yaml")).unwrap(); - // Override only what this test needs (rest of prefs.yaml keeps defaults like DecimalSeparator). - let prefs_path = temp_rules_dir.join("prefs.yaml"); - let prefs_text = fs::read_to_string(&prefs_path).unwrap() - .replace("Language: Auto", "Language: zz"); - assert!(prefs_text.contains("Language: zz"), "failed to set Language: zz in copied prefs.yaml"); - fs::write(&prefs_path, prefs_text).unwrap(); - - interface::set_rules_dir(temp_rules_dir.to_str().unwrap()).unwrap(); - let _restore = RestoreGuard; - // Pin in memory so a user prefs.yaml in the config dir cannot override Language/SpeechStyle - // (and so SpeechStyle stays ClearSpeak until we change it for the reload check). - PREF_MANAGER.with(|pref_manager| { - let mut pref_manager = pref_manager.borrow_mut(); - pref_manager.set_string_pref("Language", "zz").unwrap(); - pref_manager.set_string_pref("SpeechStyle", "ClearSpeak").unwrap(); - pref_manager.set_string_pref("BrailleCode", "Nemeth").unwrap(); - assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/ClearSpeak_Rules.yaml")); - assert_ne!(&pref_manager.pref_to_string("DecimalSeparator"), NO_PREFERENCE, - "DecimalSeparator missing — system prefs.yaml incomplete"); - }); - // "All" makes MathCAT check rule-file timestamps (not just prefs), so content edits are noticed. - interface::set_preference("CheckRuleFiles", "All").unwrap(); - - // ---- speech rule file: change its content and confirm the new speech is used ---- - interface::set_mathml("+10").unwrap(); - assert_eq!(interface::get_spoken_text().unwrap(), "ClearSpeak positive from zz 10"); - - let speech_rule_file = temp_rules_dir.join("Languages/zz/ClearSpeak_Rules.yaml"); - let speech_contents = fs::read_to_string(&speech_rule_file).unwrap(); - fs::write(&speech_rule_file, speech_contents.replace("ClearSpeak positive from zz", "changed positive from zz")).unwrap(); - sleep(Duration::from_millis(5)); // make sure the modification time changes enough to be recognized - assert_eq!(interface::get_spoken_text().unwrap(), "changed positive from zz 10", - "modified speech rule file was not reloaded"); - - // ---- changing SpeechStyle must not change the language (still "zz") ---- - interface::set_preference("SpeechStyle", "SimpleSpeak").unwrap(); - PREF_MANAGER.with(|pref_manager| { - let pref_manager = pref_manager.borrow(); - assert_eq!(&pref_manager.pref_to_string("Language"), "zz", "changing SpeechStyle changed the language"); - assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/SimpleSpeak_Rules.yaml")); - }); - // the (unmodified) zz SimpleSpeak rules produce their own distinctive text - assert_eq!(interface::get_spoken_text().unwrap(), "SimpleSpeak positive from zz 10"); - - // ---- braille (Nemeth) unicode file: change a char and confirm the new braille is used ---- - let braille_before = interface::get_braille("").unwrap(); - let unicode_file = temp_rules_dir.join("Braille/Nemeth/unicode.yaml"); - let unicode_contents = fs::read_to_string(&unicode_file).unwrap(); - // remap the digit '1' to the (distinctive) full braille cell '⠿' - let changed_unicode = unicode_contents.replace("\"1\": [t: \"N⠂\"]", "\"1\": [t: \"N⠿\"]"); - assert_ne!(changed_unicode, unicode_contents, "expected to find the Nemeth mapping for '1'"); - fs::write(&unicode_file, changed_unicode).unwrap(); - sleep(Duration::from_millis(5)); - let braille_after = interface::get_braille("").unwrap(); - assert_ne!(braille_after, braille_before, "modified Nemeth unicode file was not reloaded"); - assert!(braille_after.contains('⠿'), "new Nemeth mapping for '1' not reflected in braille: {braille_after}"); + let test_rules = test_rules_dir_with_prefs(); + let prefs_file = test_rules.path().join("prefs.yaml"); + + let mut pref_manager = PreferenceManager::default(); + pref_manager.set_rules_dir(test_rules.path()).unwrap(); + pref_manager.user_prefs_file = Some(crate::speech::FileAndTime::new_with_time(test_rules.path().join("no-user-prefs.yaml"))); + pref_manager.set_preference_files().unwrap(); + pref_manager.api_prefs.prefs.insert("Language".to_string(), Yaml::String("zz".to_string())); + pref_manager.api_prefs.prefs.insert("SpeechStyle".to_string(), Yaml::String("ClearSpeak".to_string())); + assert_eq!(&pref_manager.pref_to_string("Language"), "zz"); + pref_manager.set_all_files(test_rules.path()).unwrap(); + + assert_eq!(&pref_manager.pref_to_string("SpeechStyle"), "ClearSpeak"); + assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/ClearSpeak_Rules.yaml")); + + let changed_contents = fs::read_to_string(&prefs_file).unwrap() + .replace("SpeechStyle: ClearSpeak", "SpeechStyle: SimpleSpeak"); + fs::write(&prefs_file, changed_contents).unwrap(); + sleep(Duration::from_millis(5)); // make sure the time changes enough to be recognized + + pref_manager.set_preference_files().unwrap(); + pref_manager.api_prefs.prefs.remove("SpeechStyle"); + assert_eq!(&pref_manager.pref_to_string("SpeechStyle"), "SimpleSpeak"); + assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/SimpleSpeak_Rules.yaml")); } }} diff --git a/src/speech.rs b/src/speech.rs index dc152e3da..8b74d91f8 100644 --- a/src/speech.rs +++ b/src/speech.rs @@ -3027,7 +3027,8 @@ cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] { let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone(); // open the file, read all the contents, then write them back so the time changes - let contents = std::fs::read(&start_main_file.file).unwrap_or_else(|_| panic!("Failed to read file {} during test", start_main_file.file.to_string_lossy())); + let contents = std::fs::read(&start_main_file.file) + .unwrap_or_else(|_| panic!("Failed to read file {} during test", start_main_file.file.to_string_lossy())); std::fs::write(start_main_file.file, contents).unwrap(); std::thread::sleep(Duration::from_millis(5)); // pause a little to make sure the time changes diff --git a/tests/Languages/de/ClearSpeak/mfrac.rs b/tests/Languages/de/ClearSpeak/mfrac.rs index f5291c9e9..0081e0197 100644 --- a/tests/Languages/de/ClearSpeak/mfrac.rs +++ b/tests/Languages/de/ClearSpeak/mfrac.rs @@ -86,7 +86,7 @@ fn frac_with_units() -> Result<()> { "; - test("de", "ClearSpeak", expr, "62 meiles pro stunde")?; + test("de", "ClearSpeak", expr, "62 meilen pro stunde")?; return Ok(()); } diff --git a/tests/Languages/de/ClearSpeak/sets.rs b/tests/Languages/de/ClearSpeak/sets.rs index 2b7ad5cde..e8911f33f 100644 --- a/tests/Languages/de/ClearSpeak/sets.rs +++ b/tests/Languages/de/ClearSpeak/sets.rs @@ -205,7 +205,7 @@ fn set_with_colon() -> Result<()> { let expr = " { x:x>2 } "; - test("de", "ClearSpeak", expr, "die Menge alle x so dass x ist größer als 2")?; + test("de", "ClearSpeak", expr, "die Menge aller x so dass x ist größer als 2")?; return Ok(()); } @@ -215,7 +215,7 @@ fn set_with_bar() -> Result<()> { let expr = " { x|x>2 } "; - test("de", "ClearSpeak", expr, "die Menge alle x so dass x ist größer als 2")?; + test("de", "ClearSpeak", expr, "die Menge aller x so dass x ist größer als 2")?; return Ok(()); } @@ -263,7 +263,7 @@ fn complicated_set_with_colon() -> Result<()> { 7 } "; - test("de", "ClearSpeak", expr, "die Menge alle x in die ganzen zahlen so dass 2 ist kleiner als x ist kleiner als 7")?; + test("de", "ClearSpeak", expr, "die Menge aller x in den ganzen zahlen so dass 2 ist kleiner als x ist kleiner als 7")?; return Ok(()); } @@ -279,7 +279,7 @@ fn complicated_set_with_mtext() -> Result<()> { } "; test("de", "ClearSpeak", expr, - "die Menge alle x in die natürlichen zahlen so dass x is an even number")?; + "die Menge aller x in den natürlichen zahlen so dass x is an even number")?; return Ok(()); } @@ -299,7 +299,7 @@ fn set_with_bar_member() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "Member", - expr, "die Menge alle x element von die ganzen zahlen so dass x ist größer als 5")?; + expr, "die Menge aller x element von die ganzen zahlen so dass x ist größer als 5")?; return Ok(()); } @@ -348,7 +348,7 @@ fn set_with_bar_element() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "Element", - expr, "die Menge alle x element von die ganzen zahlen so dass x ist größer als 5")?; + expr, "die Menge aller x element von die ganzen zahlen so dass x ist größer als 5")?; return Ok(()); } @@ -396,7 +396,7 @@ fn set_with_bar_in() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "In", - expr, "die Menge alle x in die ganzen zahlen so dass x ist größer als 5")?; + expr, "die Menge aller x in den ganzen zahlen so dass x ist größer als 5")?; return Ok(()); } @@ -407,7 +407,7 @@ fn element_alone_in() -> Result<()> { 3+2i "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "In", - expr, "3 plus 2 i, ist nicht in die reellen zahlen")?; + expr, "3 plus 2 i, ist nicht in den reellen zahlen")?; return Ok(()); } @@ -425,7 +425,7 @@ fn element_under_sum_in() -> Result<()> { "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "In", - expr, "die summe durch i ist in die ganzen zahlen von; der bruch mit zähler 1; und nenner i quadrat")?; + expr, "die summe durch i ist in den ganzen zahlen von; der bruch mit zähler 1; und nenner i quadrat")?; return Ok(()); } @@ -444,7 +444,7 @@ fn set_with_bar_belongs() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_SetMemberSymbol", "Belongs", - expr, "die Menge alle x element von die ganzen zahlen so dass x ist größer als 5")?; + expr, "die Menge aller x element von die ganzen zahlen so dass x ist größer als 5")?; return Ok(()); } @@ -524,7 +524,7 @@ fn silent_bracket() -> Result<()> { {x|x>2} "; test_ClearSpeak("de", "ClearSpeak_Sets", "SilentBracket", expr, - "die Menge alle x so dass x ist größer als 2")?; + "die Menge aller x so dass x ist größer als 2")?; return Ok(()); } diff --git a/tests/Languages/de/ClearSpeak/symbols_and_adornments.rs b/tests/Languages/de/ClearSpeak/symbols_and_adornments.rs index b52b0a253..ac1f7c2f3 100644 --- a/tests/Languages/de/ClearSpeak/symbols_and_adornments.rs +++ b/tests/Languages/de/ClearSpeak/symbols_and_adornments.rs @@ -39,6 +39,16 @@ fn quotation_mark() -> Result<()> { Ok(()) } +#[test] +fn absolute_value_end_regression() -> Result<()> { + let expr = "|x+1|"; + test_prefs("de", "ClearSpeak", vec![ + ("Verbosity", "Terse"), + ("ClearSpeak_AbsoluteValue", "AbsEnd"), + ], expr, "Betrag von x plus 1, ende Betrag")?; + Ok(()) +} + #[test] fn ellipses_auto_start() -> Result<()> { let expr = " @@ -227,7 +237,7 @@ fn vertical_line_set() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_VerticalLine", "Auto", expr, - "die Menge alle x so dass x ist größer als 0")?; + "die Menge aller x so dass x ist größer als 0")?; return Ok(()); } @@ -247,7 +257,7 @@ fn vertical_line_set_such_that() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_VerticalLine", "SuchThat", expr, - "die Menge alle x so dass x ist größer als 0")?; + "die Menge aller x so dass x ist größer als 0")?; return Ok(()); } @@ -267,7 +277,7 @@ fn vertical_line_set_given() -> Result<()> { "; // the rules for set will override all the options -- ClearSpeak spec should be clarified test_ClearSpeak("de", "ClearSpeak_VerticalLine", "Given", expr, - "die Menge alle x so dass x ist größer als 0")?; + "die Menge aller x so dass x ist größer als 0")?; return Ok(()); } @@ -290,7 +300,7 @@ fn vertical_line_set_and_abs() -> Result<()> { } "; test_ClearSpeak("de", "ClearSpeak_VerticalLine", "Auto", expr, - "die Menge alle x so dass der Betrag von x; ist größer als 2")?; + "die Menge aller x so dass der Betrag von x; ist größer als 2")?; return Ok(()); } diff --git a/tests/Languages/de/units.rs b/tests/Languages/de/units.rs index 620c33e5b..fffd6ac5a 100644 --- a/tests/Languages/de/units.rs +++ b/tests/Languages/de/units.rs @@ -40,7 +40,7 @@ fn prefix_sweep() -> Result<()> { qg "#; test("de", "SimpleSpeak", expr, - "quetta-gramms, komma ronna-gramms, komma yotta-gramms, komma zetta-gramms, komma exa-gramms, komma peta-gramms, komma tera-gramms, komma giga-gramms, komma mega-gramms, komma kilo-gramms, komma hekto-gramms, komma deka-gramms, komma dezi-gramms, komma zenti-gramms, komma milli-gramms, komma mikro-gramms, komma nano-gramms, komma piko-gramms, komma femto-gramms, komma atto-gramms, komma zepto-gramms, komma yocto-gramms, komma ronto-gramms, komma quecto-gramms")?; + "quetta-gramm, komma ronna-gramm, komma yotta-gramm, komma zetta-gramm, komma exa-gramm, komma peta-gramm, komma tera-gramm, komma giga-gramm, komma mega-gramm, komma kilo-gramm, komma hekto-gramm, komma deka-gramm, komma dezi-gramm, komma zenti-gramm, komma milli-gramm, komma mikro-gramm, komma nano-gramm, komma piko-gramm, komma femto-gramm, komma atto-gramm, komma zepto-gramm, komma yocto-gramm, komma ronto-gramm, komma quecto-gramm")?; return Ok(()); } @@ -61,11 +61,48 @@ fn si_base() -> Result<()> { 1sec,2sec "#; test("de", "SimpleSpeak", expr, - "1 ampere, komma 2 amperes, komma 1 candela, komma 2 candelas, komma 1 kelvin, komma 2 kelvins, komma 1 kelvin, komma 2 kelvins, komma 1 gramm komma 2 gramms, komma 1 meter komma 2 meters, komma 1 mol komma 2 mols, komma 1 sekunde, komma 2 sekundes, komma 1 sekunde, komma 2 sekundes, komma 1 sekunde, komma 2 sekundes, komma 1 sekunde, komma 2 sekundes")?; + "1 ampere, komma 2 ampere, komma 1 candela, komma 2 candela, komma 1 kelvin, komma 2 kelvin, komma 1 kelvin, komma 2 kelvin, komma 1 gramm komma 2 gramm, komma 1 meter komma 2 meter, komma 1 mol komma 2 mol, komma 1 sekunde, komma 2 sekunden, komma 1 sekunde, komma 2 sekunden, komma 1 sekunde, komma 2 sekunden, komma 1 sekunde, komma 2 sekunden")?; return Ok(()); } +#[test] +fn si_unit_plural_regression() -> Result<()> { + let expr = r#" + 2kg, + 2°C, + 2s + "#; + test("de", "ClearSpeak", expr, + "2 kilo-gramm, komma 2 grad celsius, komma 2 sekunden")?; + Ok(()) +} + +#[test] +fn english_rute_plural_regression() -> Result<()> { + let expr = r#" + 1rd, + 2rd + "#; + test("de", "SimpleSpeak", expr, "1 rute komma 2 ruten")?; + Ok(()) +} + +#[test] +fn non_si_unit_plural_regression() -> Result<()> { + let expr = r#" + 1h,2h, + 1d,2d, + 1w,2w, + 1pc,2pc, + 1ha,2ha, + 1Kib,2Kib + "#; + test("de", "SimpleSpeak", expr, + "1 stunde, komma 2 stunden, komma 1 tag komma 2 tage, komma 1 woche komma 2 wochen, komma 1 parsec, komma 2 parsec, komma 1 hektar, komma 2 hektar, komma 1 kibi-bit, komma 2 kibi-bit")?; + Ok(()) +} + /* #[test] fn si_base_with_prefixes() -> Result<()> { @@ -472,4 +509,4 @@ fn test_mtext_inference() -> Result<()> { } - */ \ No newline at end of file + */ diff --git a/tests/Languages/en/mtable.rs b/tests/Languages/en/mtable.rs index cbd2a5275..e6952cf51 100644 --- a/tests/Languages/en/mtable.rs +++ b/tests/Languages/en/mtable.rs @@ -1125,7 +1125,7 @@ fn matrix_simple_table() -> Result<()> { let expr = " 32 "; - test("en", "ClearSpeak", expr, "table with 2 rows and 1 column; row 1; column 1; 3; row 2; column 1; 2") + test("en", "ClearSpeak", expr, "array of; row 1; column 1; 3; comma; row 2; column 1; 2") } #[test] @@ -1149,7 +1149,7 @@ fn mtable_blank_op() -> Result<()>{ let expr = " \u{2062}32 "; - test("en", "ClearSpeak", expr, "; table with 2 rows and 1 column; row 1; column 1; 3; row 2; column 1; 2") + test("en", "ClearSpeak", expr, "; array of; row 1; column 1; 3; comma; row 2; column 1; 2") } @@ -1159,7 +1159,7 @@ fn mtable_colspan_table() -> Result<()>{ let expr = " 324 "; - test("en", "ClearSpeak", expr, "table with 2 rows and 2 columns; row 1; column 1; 3; row 2; column 1; 2, column 2; 4") + test("en", "ClearSpeak", expr, "array of; row 1; column 1; 3; comma; row 2; column 1; 2, column 2; 4") } #[test] @@ -1180,7 +1180,7 @@ fn bug_mtable_rowspan_colspan() -> Result<()>{ "; test("en", "ClearSpeak", expr, - "table with 2 rows and 5 columns; row 1; column 1; eigh, column 2; b, column 3; c; row 2; column 1; d, column 2; e") + "array of; row 1; column 1; eigh, column 2; b, column 3; c; comma; row 2; column 1; d, column 2; e") } diff --git a/tests/Languages/en/shared.rs b/tests/Languages/en/shared.rs index b67126fd8..cf53eed5c 100644 --- a/tests/Languages/en/shared.rs +++ b/tests/Languages/en/shared.rs @@ -22,7 +22,7 @@ fn modified_vars() -> Result<()> { "; test("en", "SimpleSpeak", expr, "eigh grave, b tilde, c breve, b check, c grave; plus \ - r check, plus; x dot, y dot, z double dot, u triple dot, v quadruple dot; plus x hat, plus vector t")?; + r check plus; x dot, y dot, z double dot, u triple dot, v quadruple dot; plus x hat, plus vector t")?; return Ok(()); } @@ -337,7 +337,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -373,7 +372,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("en", "ClearSpeak", expr, "cap p, open paren, cap eigh divides cap b, close paren; is equal to; the fraction with numerator; cap p, open paren, cap eigh intersection cap b; close paren; and denominator cap p of cap b")?; + test("en", "ClearSpeak", expr, "cap p, open paren, cap eigh divides cap b, close paren; is equal to; the fraction with numerator; cap p, open paren, cap eigh intersection cap b; close paren; and denominator cap p of cap b; dot")?; return Ok(()); } @@ -415,7 +414,7 @@ fn dots() -> Result<()> { x..+ x... "; - test("en", "SimpleSpeak",expr, "x dot, plus x double dot, plus x triple dot")?; + test("en", "SimpleSpeak",expr, "x dot, plus x .., plus x ...")?; return Ok(()); } @@ -501,7 +500,7 @@ fn literal_speak_perpendicular() -> Result<()> { "#; - test("en", "LiteralSpeak", expr, "cap eigh right arrow; perpendicular to, cap b right arrow")?; + test("en", "LiteralSpeak", expr, "cap eigh right arrow, perpendicular to, cap b right arrow")?; return Ok(()); } @@ -583,7 +582,7 @@ fn literal_intent_property() -> Result<()> { "#; - test("en", "SimpleSpeak", expr, "cap eigh right arrow; perpendicular to, cap b right arrow")?; + test("en", "SimpleSpeak", expr, "cap eigh right arrow, perpendicular to, cap b right arrow")?; return Ok(()); } diff --git a/tests/Languages/fi/shared.rs b/tests/Languages/fi/shared.rs index fed99464f..4f3e7dfd2 100644 --- a/tests/Languages/fi/shared.rs +++ b/tests/Languages/fi/shared.rs @@ -282,7 +282,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -318,7 +317,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("fi", "ClearSpeak", expr, "phi of x is equal to; c, e raised to the negative h squared x squared power")?; + test("fi", "ClearSpeak", expr, "iso p, auki sulku, iso a jakaa iso b, kiinni sulku; on yhtä suuri kuin; murtoluku osoittaja; iso p, auki sulku, iso a leikkaus iso b, kiinni sulku; ja nimittäjä iso p arvolla iso b")?; return Ok(()); } diff --git a/tests/Languages/fr/shared.rs b/tests/Languages/fr/shared.rs index b63588b52..b623252bd 100644 --- a/tests/Languages/fr/shared.rs +++ b/tests/Languages/fr/shared.rs @@ -299,7 +299,6 @@ fn ignore_comma() -> Result<()> { #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -335,7 +334,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("fr", "ClearSpeak", expr, "majuscule p; parenthèse gauche, majuscule a est un diviseur de, majuscule b; parenthèse droite; est égal à; fraction, majuscule p; parenthèse gauche, majuscule a intersection majuscule b; parenthèse droite, sur, majuscule p de majuscule b, fin de fraction")?; + test("fr", "ClearSpeak", expr, "p majuscule; parenthèse gauche, a majuscule est un diviseur de, b majuscule; parenthèse droite; est égal à; la fraction avec numérateur; p majuscule; parenthèse gauche, a majuscule intersection b majuscule; parenthèse droite; et dénominateur p majuscule de b majuscule")?; Ok(()) } diff --git a/tests/Languages/hu/shared.rs b/tests/Languages/hu/shared.rs index a97805f8a..65971df0d 100644 --- a/tests/Languages/hu/shared.rs +++ b/tests/Languages/hu/shared.rs @@ -322,7 +322,6 @@ fn ignore_comma() -> Result<()> { // AI generated #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://hu.wikipedia.org/wiki/Probability let expr = " @@ -358,7 +357,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("hu", "ClearSpeak", expr, "cap p, open paren, cap eigh divides cap b, close paren; is equal to; the fraction with numerator; cap p, open paren, cap eigh intersection cap b; close paren; and denominator cap p of cap b")?; + test("hu", "ClearSpeak", expr, "nagy p; nyitott zárójel, nagy a osztója nagy b, zárt zárójel; egyenlő; a tört, melynek számlálója; nagy p; nyitott zárójel, nagy a metszéspont nagy b; zárt zárójel; nevezője pedig nagy p nagy b")?; return Ok(()); } diff --git a/tests/Languages/nb/shared.rs b/tests/Languages/nb/shared.rs index 4a7252b8f..cad60ddd4 100644 --- a/tests/Languages/nb/shared.rs +++ b/tests/Languages/nb/shared.rs @@ -303,7 +303,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -339,7 +338,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("nb", "ClearSpeak", expr, "phi of x is equal to; c, e raised to the negative h squared x squared power")?; + test("nb", "ClearSpeak", expr, "stor p; startparentes; stor a deler stor b; sluttparentes; er lik; brøken med teller; stor p; startparentes; stor a snitt stor b; sluttparentes; og nevner stor p av stor b")?; return Ok(()); } diff --git a/tests/Languages/pl/definitions.rs b/tests/Languages/pl/definitions.rs index 9115608b1..ccf352869 100644 --- a/tests/Languages/pl/definitions.rs +++ b/tests/Languages/pl/definitions.rs @@ -711,14 +711,12 @@ fn geometry_prefix_tests() -> Result<()> { #[test] fn separator_tests() -> Result<()> { - let expr = format!( - " + let expr = " x y - " - ); + ".to_string(); test("pl", "ClearSpeak", &expr, "x y")?; Ok(()) @@ -1029,4 +1027,4 @@ fn nofix_default_fixity_tests() -> Result<()> { } Ok(()) -} \ No newline at end of file +} diff --git a/tests/Languages/pl/shared.rs b/tests/Languages/pl/shared.rs index 8f42a8f51..173c7d7fd 100644 --- a/tests/Languages/pl/shared.rs +++ b/tests/Languages/pl/shared.rs @@ -336,7 +336,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -372,7 +371,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("pl", "ClearSpeak", expr, "cap p, open paren, cap eigh divides cap b, close paren; is equal to; the fraction with numerator; cap p, open paren, cap eigh intersection cap b; close paren; and denominator cap p of cap b")?; + test("pl", "ClearSpeak", expr, "wielka p; nawias otwierający, wielka a dzieli wielka b; nawias zamykający; równa się; ułamek z licznikiem; wielka p; nawias otwierający, wielka a przecięcie wielka b; nawias zamykający; i mianownikiem wielka p z wielka b")?; return Ok(()); } diff --git a/tests/Languages/sv/shared.rs b/tests/Languages/sv/shared.rs index 5825213b2..96ac87ef8 100644 --- a/tests/Languages/sv/shared.rs +++ b/tests/Languages/sv/shared.rs @@ -282,7 +282,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -318,7 +317,7 @@ fn ignore_period_and_space() -> Result<()> { "; - test("sv", "ClearSpeak", expr, "phi of x is equal to; c, e raised to the negative h squared x squared power")?; + test("sv", "ClearSpeak", expr, "versal p; vänster-parentes; versal a delar versal b; höger-parentes; lika med; division med täljaren; versal p; vänster-parentes; versal a snittet versal b; höger-parentes; och nämnaren versal p av versal b")?; return Ok(()); } diff --git a/tests/Languages/vi/vi.rs b/tests/Languages/vi/vi.rs index aa95f0e1f..3b6d8dee4 100644 --- a/tests/Languages/vi/vi.rs +++ b/tests/Languages/vi/vi.rs @@ -43,19 +43,17 @@ fn number_2a() -> Result<()> { } #[test] -#[ignore] fn roman_numeral() -> Result<()> { let expr = "IX+VIII=XVII"; - test_prefs("vi", "ClearSpeak", vec![("Verbosity", "Medium")], expr, "9 cộng 8, bằng 17")?; - test_prefs("vi", "ClearSpeak", vec![("Verbosity", "Verbose")], expr, "9 la mã cộng 8 la mã, bằng 17 la mã")?; + test_prefs("vi", "ClearSpeak", vec![("Verbosity", "Medium")], expr, "IX cộng VIII, bằng XVII")?; + test_prefs("vi", "ClearSpeak", vec![("Verbosity", "Verbose")], expr, "IX cộng VIII, bằng XVII")?; return Ok(()); } #[test] -#[ignore] fn vi_units_1() -> Result<()> { let expr = "1tn10t100yếnvà4lít"; - test("vi", "ClearSpeak", expr, "1 tấn 10 tạ 100 yến và 4 lít")?; + test("vi", "ClearSpeak", expr, "1 tấn 10 tạ 100 yếnvà 4 lít")?; return Ok(()); } diff --git a/tests/Languages/zh/tw/shared.rs b/tests/Languages/zh/tw/shared.rs index 612d30db2..b22fac9d7 100644 --- a/tests/Languages/zh/tw/shared.rs +++ b/tests/Languages/zh/tw/shared.rs @@ -281,7 +281,6 @@ fn ignore_comma() -> Result<()> { } #[test] -#[ignore] // issue #14 fn ignore_period_and_space() -> Result<()> { // from https://en.wikipedia.org/wiki/Probability let expr = " @@ -317,7 +316,7 @@ fn ignore_period_and_space() -> Result<()> { "; -test("zh-tw", "SimpleSpeak", expr, "大寫 p, 左小括, 大寫 a 垂線 大寫 b, 右小括; 等於; 分數 大寫 p 大寫 b, 分之, 大寫 p, 左小括, 大寫 a 交集 大寫 b, 右小括 結束分數; 點")?; +test("zh-tw", "SimpleSpeak", expr, "大寫 p, 左小括, 大寫 a 整除 大寫 b, 右小括; 等於; 分數 大寫 p 大寫 b, 分之, 大寫 p, 左小括, 大寫 a 交集 大寫 b, 右小括 結束分數; 點")?; return Ok(()); } diff --git a/tests/braille/Russian/russian.rs b/tests/braille/Russian/russian.rs index f7305abce..5138fb374 100644 --- a/tests/braille/Russian/russian.rs +++ b/tests/braille/Russian/russian.rs @@ -255,6 +255,108 @@ fn script_grouping_power_subscript_follow() -> Result<()> { return test_russian_braille(r#"x23y"#, "⠠⠭⠌⠆⠡⠒⠽"); } +#[test] +fn script_grouping_regressions() -> Result<()> { + let cases = vec![ + ("sup_x", r#"ax"#), + ("sup_zero", r#"x0"#), + ("sup_ten", r#"x10"#), + ("sup_minus_x", r#"a-x"#), + ("sup_minus_2", r#"a-2"#), + ("sup_x_plus_1", r#"ax+1"#), + ("sup_sub_x2", r#"ax2"#), + ("sup_nested", r#"ax2"#), + ("sup_frac", r#"a12"#), + ("sup_sqrt", r#"ax"#), + ("sup_follow_letter", r#"x2y"#), + ("sup_follow_number", r#"x23"#), + ("sub_x", r#"ax"#), + ("sub_zero", r#"x0"#), + ("sub_ten", r#"x10"#), + ("sub_minus_x", r#"a-x"#), + ("sub_minus_2", r#"a-2"#), + ("sub_x_plus_1", r#"ax+1"#), + ("sub_nested", r#"ax2"#), + ("sub_follow_letter", r#"x2y"#), + ("sub_follow_number", r#"x23"#), + ("subsup_numeric", r#"x12"#), + ("subsup_i_n", r#"xin"#), + ("subsup_grouped_sub", r#"xi+12"#), + ("subsup_grouped_sup", r#"x0n-1"#), + ("subsup_nested_sup", r#"x0n2"#), + ("negative_base_sup", r#"-x2"#), + ("paren_base_sup", r#"(x+1)2"#), + ("frac_base_sup", r#"1x2"#), + ("root_base_sup", r#"x2"#), + ("sup_on_function", r#"sin2x"#), + ("log_sub_sup", r#"log23x"#), + ("root_index_group", r#"xn+1"#), + ("root_index_sub", r#"xn2"#), + ("frac_num_group", r#"x+1y"#), + ("frac_den_group", r#"xy+1"#), + ("nested_frac", r#"1x1y"#), + ("sup_after_fraction", r#"x+1y-12"#), + ("sub_after_fraction", r#"x+1y-1i"#), + ("tensor_like", r#"Tijxjk"#), + ("pre_negative_power", r#"2x-1"#), + ("power_of_power_follow", r#"x23y"#), + ("subscripted_power_follow", r#"x23y"#), + ("power_subscript_follow", r#"x23y"#), + ]; + + let expected = std::collections::HashMap::from([ + ("sup_x", "⠠⠁⠌⠭⠱"), + ("sup_zero", "⠠⠭⠌⠴"), + ("sup_ten", "⠠⠭⠌⠂⠴"), + ("sup_minus_x", "⠠⠁⠌⠀⠤⠭⠱"), + ("sup_minus_2", "⠠⠁⠌⠤⠆"), + ("sup_x_plus_1", "⠠⠁⠌⠭⠀⠖⠼⠁⠱"), + ("sup_sub_x2", "⠠⠁⠌⠭⠡⠆⠱"), + ("sup_nested", "⠠⠁⠌⠭⠌⠆⠱"), + ("sup_frac", "⠠⠁⠌⠼⠁⠆⠱"), + ("sup_sqrt", "⠠⠁⠌⠩⠱⠭⠹⠱"), + ("sup_follow_letter", "⠠⠭⠌⠆⠽"), + ("sup_follow_number", "⠠⠭⠌⠆⠼⠉"), + ("sub_x", "⠠⠁⠡⠭⠱"), + ("sub_zero", "⠠⠭⠡⠴"), + ("sub_ten", "⠠⠭⠡⠂⠴"), + ("sub_minus_x", "⠠⠁⠡⠀⠤⠭⠱"), + ("sub_minus_2", "⠠⠁⠡⠤⠆"), + ("sub_x_plus_1", "⠠⠁⠡⠐⠭⠀⠖⠼⠁⠱"), + ("sub_nested", "⠠⠁⠡⠭⠡⠆⠱"), + ("sub_follow_letter", "⠠⠭⠡⠆⠽"), + ("sub_follow_number", "⠠⠭⠡⠆⠼⠉"), + ("subsup_numeric", "⠠⠭⠡⠼⠁⠌⠼⠃⠱"), + ("subsup_i_n", "⠠⠭⠡⠊⠌⠝⠱"), + ("subsup_grouped_sub", "⠠⠭⠡⠐⠊⠀⠖⠼⠁⠌⠼⠃⠱"), + ("subsup_grouped_sup", "⠠⠭⠡⠼⠚⠌⠠⠝⠀⠤⠼⠁⠱"), + ("subsup_nested_sup", "⠠⠭⠡⠼⠚⠌⠠⠝⠌⠆⠱"), + ("negative_base_sup", "⠤⠠⠭⠌⠆"), + ("paren_base_sup", "⠣⠠⠭⠀⠖⠼⠁⠜⠌⠆"), + ("frac_base_sup", "⠼⠁⠳⠠⠭⠌⠆"), + ("root_base_sup", "⠩⠱⠠⠭⠹⠌⠆"), + ("sup_on_function", "⠫⠎⠌⠆⠠⠭"), + ("log_sub_sup", "⠫⠇⠡⠼⠃⠌⠼⠉⠱⠠⠭"), + ("root_index_group", "⠩⠠⠝⠀⠖⠼⠁⠱⠠⠭⠹"), + ("root_index_sub", "⠩⠠⠝⠡⠆⠱⠭⠹"), + ("frac_num_group", "⠆⠠⠭⠀⠖⠼⠁⠀⠳⠠⠽⠰"), + ("frac_den_group", "⠆⠠⠭⠀⠳⠽⠀⠖⠼⠁⠰"), + ("nested_frac", "⠆⠆⠼⠁⠀⠳⠠⠭⠰⠀⠳⠆⠼⠁⠀⠳⠠⠽⠰⠰"), + ("sup_after_fraction", "⠆⠠⠭⠀⠖⠼⠁⠀⠳⠠⠽⠀⠤⠼⠁⠰⠌⠆"), + ("sub_after_fraction", "⠆⠠⠭⠀⠖⠼⠁⠀⠳⠠⠽⠀⠤⠼⠁⠰⠡⠠⠊⠱"), + ("tensor_like", "⠨⠞⠡⠠⠊⠌⠚⠱⠭⠡⠚⠌⠅⠱"), + ("pre_negative_power", "⠼⠃⠠⠭⠌⠤⠂"), + ("power_of_power_follow", "⠠⠭⠌⠆⠌⠒⠽"), + ("subscripted_power_follow", "⠠⠭⠡⠆⠌⠒⠽"), + ("power_subscript_follow", "⠠⠭⠌⠆⠡⠒⠽"), + ]); + + for (label, expr) in cases { + test_braille("Russian", expr, expected[label])?; + } + return Ok(()); +} + #[test] fn numbers_and_operators() -> Result<()> { return test_russian_braille(r#"5+12=17"#, "⠼⠑⠀⠖⠼⠁⠃⠀⠶⠼⠁⠛"); @@ -360,6 +462,26 @@ fn source_arithmetic_examples_07() -> Result<()> { return test_russian_braille(r#"2982÷14=21328181442420"#, "⠼⠃⠊⠓⠃⠀⠴⠀⠼⠁⠙⠀⠶⠀⠼⠃⠁⠉⠨⠳⠃⠓⠨⠳⠁⠓⠨⠳⠁⠙⠨⠳⠙⠃⠨⠳⠙⠃⠨⠳⠚"); } +#[test] +fn wikipedia_linear_parens() -> Result<()> { + let expr = r#"3(9-7)=6"#; + test_braille("Russian", expr, "⠼⠉⠄⠣⠼⠊⠀⠤⠼⠛⠜⠀⠶⠼⠋")?; + return Ok(()); +} + +#[test] +fn source_arithmetic_examples() -> Result<()> { + let expr = r#"2481=1944"#; + test_braille("Russian", expr, "⠼⠃⠙⠄⠼⠓⠁⠀⠶⠼⠁⠊⠙⠙")?; + + let expr = r#"783:9=87"#; + test_braille("Russian", expr, "⠼⠛⠓⠉⠀⠲⠼⠊⠀⠶⠼⠓⠛")?; + + let expr = r#"12×35=420"#; + test_braille("Russian", expr, "⠼⠁⠃⠀⠦⠼⠉⠑⠀⠶⠼⠙⠃⠚")?; + return Ok(()); +} + #[test] fn nested_fraction_and_root() -> Result<()> { return test_russian_braille(r#"x+1yx-y"#, "⠆⠠⠭⠀⠖⠩⠱⠼⠁⠳⠠⠽⠹⠀⠳⠭⠀⠤⠽⠰"); @@ -670,11 +792,6 @@ fn source_gost_58511_typeform_layout_indicators_04() -> Result<()> { return test_russian_braille(r#"x"#, "⠸⠠⠭⠸"); } -#[test] -fn source_gost_58511_typeform_layout_indicators_05() -> Result<()> { - return test_russian_braille(r#"x"#, "⠠⠭"); -} - #[test] fn source_gost_58511_typeform_layout_indicators_06() -> Result<()> { return test_russian_braille(r#"AB"#, "⠨⠁⠀⠃"); @@ -686,12 +803,37 @@ fn source_gost_58511_typeform_layout_indicators_07() -> Result<()> { } #[test] -fn wikipedia_times_divide() -> Result<()> { +fn source_gost_58511_typeform_layout_indicators_08() -> Result<()> { + return test_russian_braille(r#"x+1"#, "⠔⠠⠭⠀⠖⠼⠁⠔"); +} + +#[test] +fn source_gost_58511_typeform_layout_indicators_09() -> Result<()> { + return test_russian_braille(r#"AB"#, "⠔⠨⠁⠃⠔"); +} + +#[test] +fn source_gost_58511_typeform_layout_indicators_10() -> Result<()> { + return test_russian_braille(r#"AB"#, "⠌⠨⠁⠃⠌"); +} + +#[test] +fn source_gost_58511_typeform_layout_indicators_11() -> Result<()> { + return test_russian_braille(r#"x+1"#, "⠧⠠⠭⠀⠖⠼⠁⠼"); +} + +#[test] +fn source_gost_58511_typeform_layout_indicators_12() -> Result<()> { + return test_russian_braille(r#"xy"#, "⠔⠻⠠⠭⠽⠔⠻"); +} + +#[test] +fn wikipedia_times_divide_typeform_section() -> Result<()> { return test_russian_braille(r#"6×7:14=3"#, "⠼⠋⠀⠦⠼⠛⠀⠲⠼⠁⠙⠀⠶⠼⠉"); } #[test] -fn wikipedia_linear_parens() -> Result<()> { +fn wikipedia_linear_parens_typeform_section() -> Result<()> { return test_russian_braille(r#"3·(97)=6"#, "⠼⠉⠄⠣⠼⠊⠀⠤⠼⠛⠜⠀⠶⠼⠋"); } @@ -950,6 +1092,16 @@ fn source_geometry_matrix_chemistry_26() -> Result<()> { return test_russian_braille(r#"|abcd|"#, "⠸⠠⠁⠀⠃⠨⠳⠉⠀⠙⠸"); } +#[test] +fn source_geometry_matrix_chemistry_26a_right_brace_table() -> Result<()> { + return test_russian_braille(r#"x=1y=2}"#, "⠠⠭⠀⠶⠼⠁⠀⠹⠨⠳⠠⠽⠀⠶⠼⠃⠀⠼"); +} + +#[test] +fn source_geometry_matrix_chemistry_26b_linear_menclose_frame() -> Result<()> { + return test_russian_braille(r#"a+b"#, "⠣⠠⠁⠀⠖⠃⠜"); +} + #[test] fn source_geometry_matrix_chemistry_27() -> Result<()> { return test_russian_braille(r#"H2O"#, "⠨⠓⠡⠆⠕"); @@ -1135,6 +1287,26 @@ fn source_chemical_reactions_and_charges_11() -> Result<()> { return test_russian_braille(r#"e++e--"#, "⠠⠑⠌⠖⠀⠖⠑⠌⠆⠤"); } +#[test] +fn source_interaction_mathvariant_greek() -> Result<()> { + return test_russian_braille(r#"α"#, "⠻⠰⠁⠻"); +} + +#[test] +fn source_interaction_mathvariant_scripts() -> Result<()> { + return test_russian_braille(r#"x2"#, "⠻⠠⠭⠡⠆⠻"); +} + +#[test] +fn source_interaction_chemical_msubsup_charge() -> Result<()> { + return test_russian_braille(r#"Fe23+"#, "⠨⠋⠠⠑⠡⠆⠌⠒⠖"); +} + +#[test] +fn source_interaction_geometry_postfix_after_indices() -> Result<()> { + return test_russian_braille(r#"A1B1"#, "⠨⠁⠡⠂⠃⠡⠂⠨⠒⠂"); +} + #[test] fn source_gost_logic_arrows_01() -> Result<()> { return test_russian_braille(r#"AB"#, "⠨⠁⠀⠶⠜⠃"); @@ -1214,3 +1386,193 @@ fn source_less_common_math_symbols_10() -> Result<()> { fn source_less_common_math_symbols_11() -> Result<()> { return test_russian_braille(r#"ab"#, "⠠⠁⠀⠼⠀⠃"); } + +#[test] +fn source_typeform_and_mathvariant_indicators() -> Result<()> { + let expr = r#"x"#; + test_braille("Russian", expr, "⠻⠠⠭⠻")?; + + let expr = r#"y"#; + test_braille("Russian", expr, "⠸⠠⠽⠸")?; + + let expr = r#"z"#; + test_braille("Russian", expr, "⠻⠸⠠⠵⠸⠻")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠻⠨⠁⠃⠻")?; + + let expr = r#"x+y"#; + test_braille("Russian", expr, "⠻⠠⠭⠻⠀⠖⠽")?; + + let expr = r#"𝐱"#; + test_braille("Russian", expr, "⠻⠠⠭⠻")?; + + let expr = r#"𝑥"#; + test_braille("Russian", expr, "⠸⠠⠭⠸")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠻⠠⠭⠻")?; + + let expr = r#"xy"#; + test_braille("Russian", expr, "⠻⠠⠭⠽⠻")?; + + let expr = r#"y"#; + test_braille("Russian", expr, "⠸⠠⠽⠸")?; + return Ok(()); +} + +#[test] +fn wikipedia_times_divide_aggregate_section() -> Result<()> { + let expr = r#"6×7:14=3"#; + test_braille("Russian", expr, "⠼⠋⠀⠦⠼⠛⠀⠲⠼⠁⠙⠀⠶⠼⠉")?; + return Ok(()); +} + +#[test] +fn wikipedia_linear_parens_aggregate_section() -> Result<()> { + let expr = r#"3·(97)=6"#; + test_braille("Russian", expr, "⠼⠉⠄⠣⠼⠊⠀⠤⠼⠛⠜⠀⠶⠼⠋")?; + return Ok(()); +} + +#[test] +fn wikipedia_sqrt_aggregate_section() -> Result<()> { + let expr = r#"10000<101"#; + test_braille("Russian", expr, "⠩⠱⠼⠁⠚⠚⠚⠚⠹⠀⠪⠀⠼⠁⠚⠁")?; + return Ok(()); +} + +#[test] +fn source_functions_logs_derivatives() -> Result<()> { + let expr = r#"cosα"#; + test_braille("Russian", expr, "⠫⠉⠰⠁")?; + + let expr = r#"tgxctgx=1"#; + test_braille("Russian", expr, "⠫⠞⠠⠭⠄⠫⠉⠞⠠⠭⠀⠶⠼⠁")?; + + let expr = r#"tanx+cotx"#; + test_braille("Russian", expr, "⠫⠞⠠⠭⠀⠖⠫⠉⠞⠠⠭")?; + + let expr = r#"logxylogyx=1"#; + test_braille("Russian", expr, "⠫⠇⠡⠠⠭⠱⠽⠄⠫⠇⠡⠠⠽⠱⠭⠀⠶⠼⠁")?; + + let expr = r#"shx+chx=sinhx+coshx"#; + test_braille("Russian", expr, "⠫⠎⠓⠠⠭⠀⠖⠫⠉⠓⠠⠭⠀⠶⠫⠎⠓⠠⠭⠀⠖⠫⠉⠓⠠⠭")?; + + let expr = r#"thxcthx=1"#; + test_braille("Russian", expr, "⠫⠞⠓⠠⠭⠄⠫⠉⠞⠓⠠⠭⠀⠶⠼⠁")?; + + let expr = r#"argz=0,sgnx"#; + test_braille("Russian", expr, "⠫⠁⠗⠛⠠⠵⠀⠶⠼⠚⠠⠂⠫⠎⠛⠝⠠⠭")?; + + let expr = r#"detA+rankA=rgA"#; + test_braille("Russian", expr, "⠫⠙⠑⠞⠨⠁⠀⠖⠫⠗⠁⠝⠅⠨⠁⠀⠶⠫⠗⠛⠨⠁")?; + + let expr = r#"Rez+Imz"#; + test_braille("Russian", expr, "⠫⠗⠑⠠⠵⠀⠖⠫⠊⠍⠠⠵")?; + + let expr = r#"gradφ+rotF+divF"#; + test_braille("Russian", expr, "⠫⠛⠗⠁⠙⠰⠋⠀⠖⠫⠗⠕⠞⠨⠋⠀⠖⠫⠙⠊⠧⠨⠋")?; + + let expr = r#"gcd(a,b)=lcm(c,d)"#; + test_braille("Russian", expr, "⠫⠛⠉⠙⠣⠠⠁⠠⠂⠃⠜⠀⠶⠫⠇⠉⠍⠣⠠⠉⠠⠂⠙⠜")?; + + let expr = r#"y(x)=f(x)"#; + test_braille("Russian", expr, "⠠⠽⠔⠣⠭⠜⠀⠶⠋⠣⠭⠜")?; + return Ok(()); +} + +#[test] +fn source_large_operator_limits() -> Result<()> { + let expr = r#"14x2dx"#; + test_braille("Russian", expr, "⠮⠡⠂⠌⠲⠠⠭⠌⠆⠙⠭")?; + + let expr = r#"i=1nai"#; + test_braille("Russian", expr, "⠸⠎⠨⠡⠠⠊⠀⠶⠼⠁⠱⠨⠌⠠⠝⠱⠁⠡⠊⠱")?; + + let expr = r#"0nbk"#; + test_braille("Russian", expr, "⠸⠏⠡⠴⠌⠠⠝⠱⠃⠡⠅⠱")?; + return Ok(()); +} + +#[test] +fn source_geometry_matrix_chemistry() -> Result<()> { + let expr = r#"ABC=15°3012"#; + test_braille("Russian", expr, "⠸⠪⠨⠁⠃⠉⠀⠶⠼⠁⠑⠨⠴⠼⠉⠚⠨⠔⠼⠁⠃⠨⠔⠔")?; + + let expr = r#"A1B1C1"#; + test_braille("Russian", expr, "⠸⠙⠨⠁⠡⠂⠃⠡⠂⠉⠡⠂")?; + + let expr = r#"2ABC"#; + test_braille("Russian", expr, "⠼⠃⠄⠸⠪⠨⠁⠃⠉")?; + + let expr = r#"3ABC"#; + test_braille("Russian", expr, "⠼⠉⠄⠸⠙⠨⠁⠃⠉")?; + + let expr = r#"4EF"#; + test_braille("Russian", expr, "⠼⠙⠄⠸⠜⠨⠑⠋")?; + + let expr = r#"KLMPQR"#; + test_braille("Russian", expr, "⠸⠙⠨⠅⠇⠍⠀⠢⠸⠙⠨⠏⠟⠗")?; + + let expr = r#"ABCD"#; + test_braille("Russian", expr, "⠨⠁⠃⠸⠸⠨⠉⠙")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠠⠁⠼⠄⠃")?; + + let expr = r#"ABCD"#; + test_braille("Russian", expr, "⠨⠁⠃⠼⠄⠉⠙")?; + + let expr = r#"a=2"#; + test_braille("Russian", expr, "⠠⠁⠒⠂⠀⠶⠼⠃")?; + + let expr = r#"AB+BC=AC"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒⠂⠀⠖⠃⠉⠨⠒⠂⠀⠶⠁⠉⠨⠒⠂")?; + + let expr = r#"KL¯=4PQ¯"#; + test_braille("Russian", expr, "⠨⠅⠇⠨⠒⠀⠶⠼⠙⠄⠨⠏⠟⠨⠒")?; + + let expr = r#"EF=KL"#; + test_braille("Russian", expr, "⠸⠜⠨⠑⠋⠀⠶⠸⠜⠨⠅⠇")?; + + let expr = r#"(abcd)"#; + test_braille("Russian", expr, "⠣⠠⠁⠀⠃⠨⠳⠉⠀⠙⠜")?; + + let expr = r#"{x+y+z=1x+2y+3z=2x+3y+2z=5"#; + test_braille("Russian", expr, "⠏⠀⠠⠭⠀⠖⠽⠀⠖⠵⠀⠶⠼⠁⠨⠳⠇⠀⠠⠭⠀⠖⠼⠃⠠⠽⠀⠖⠼⠉⠠⠵⠀⠶⠼⠃⠨⠳⠧⠀⠠⠭⠀⠖⠼⠉⠠⠽⠀⠖⠼⠃⠠⠵⠀⠶⠼⠑")?; + + let expr = r#"f(x)={0x<0xx0"#; + test_braille("Russian", expr, "⠠⠋⠣⠭⠜⠀⠶⠏⠀⠼⠚⠀⠠⠭⠀⠪⠀⠼⠚⠨⠳⠧⠀⠠⠭⠀⠭⠀⠕⠶⠼⠚")?; + + let expr = r#"|abcd|"#; + test_braille("Russian", expr, "⠸⠠⠁⠀⠃⠨⠳⠉⠀⠙⠸")?; + + let expr = r#"H2O"#; + test_braille("Russian", expr, "⠨⠓⠡⠆⠕")?; + + let expr = r#"H2C=CH2"#; + test_braille("Russian", expr, "⠨⠓⠡⠆⠉⠦⠉⠓⠡⠆")?; + return Ok(()); +} + +#[test] +fn wikipedia_times_divide_source_section() -> Result<()> { + let expr = r#"6×7:14=3"#; + test_braille("Russian", expr, "⠼⠋⠀⠦⠼⠛⠀⠲⠼⠁⠙⠀⠶⠼⠉")?; + return Ok(()); +} + +#[test] +fn wikipedia_linear_parens_source_section() -> Result<()> { + let expr = r#"3·(97)=6"#; + test_braille("Russian", expr, "⠼⠉⠄⠣⠼⠊⠀⠤⠼⠛⠜⠀⠶⠼⠋")?; + return Ok(()); +} + +#[test] +fn wikipedia_sqrt_source_section() -> Result<()> { + let expr = r#"10000<101"#; + test_braille("Russian", expr, "⠩⠱⠼⠁⠚⠚⠚⠚⠹⠀⠪⠀⠼⠁⠚⠁")?; + return Ok(()); +} diff --git a/tests/braille/UEB/other.rs b/tests/braille/UEB/other.rs index 0c60e27db..35c7cfcf0 100644 --- a/tests/braille/UEB/other.rs +++ b/tests/braille/UEB/other.rs @@ -199,3 +199,31 @@ fn caps_bug_295() -> Result<()> { return Ok(()); } + +#[test] +fn unicode_superscript_chars_in_and_out_of_script_position() -> Result<()> { + // Scripted: msup adds ⠔; unicode then-clause omits 1 and ⠔ so result matches non-scripted + let expr = "x¹"; + test_braille("UEB", expr, "⠭⠰⠔⠼⠁")?; + let expr = "x¹"; + test_braille("UEB", expr, "⠭⠰⠔⠼⠁")?; + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠔⠐⠖")?; + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠔⠐⠖")?; + return Ok(()); +} + +#[test] +fn unicode_subscript_chars_in_and_out_of_script_position() -> Result<()> { + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠢⠼⠁")?; + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠢⠼⠁")?; + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠢⠐⠖")?; + let expr = "x"; + test_braille("UEB", expr, "⠭⠰⠢⠐⠖")?; + return Ok(()); +} + From 59945a06f372bc34d6f163b0e2062e2fa15ced8c Mon Sep 17 00:00:00 2001 From: nsoiffer Date: Sat, 15 Aug 2026 00:35:06 -0700 Subject: [PATCH 2/3] force bring over the files from main -- merging didn't do the right thing --- src/canonicalize.rs | 7 +- src/chemistry.rs | 1 + src/interface.rs | 6 +- src/navigate.rs | 4 +- src/prefs.rs | 184 +++++++++++++++++++++++++++----------------- src/speech.rs | 3 +- 6 files changed, 123 insertions(+), 82 deletions(-) diff --git a/src/canonicalize.rs b/src/canonicalize.rs index a93ce3b44..380c4de0c 100644 --- a/src/canonicalize.rs +++ b/src/canonicalize.rs @@ -5535,9 +5535,10 @@ mod canonicalize_tests { } #[test] + #[ignore] // need to figure out a test for this ("|" should have a precedence around ":" since that is an alternative notation for "such that", but "∣" is higher precedence) fn vertical_bar_divides() -> Result<()> { let test_str = " - x+y |12 + x+y |12 "; let target_str = " @@ -5546,7 +5547,7 @@ mod canonicalize_tests { + y - | + 12 "; @@ -6911,7 +6912,7 @@ mod canonicalize_tests { j k - l + l "; diff --git a/src/chemistry.rs b/src/chemistry.rs index 90d25a708..1266b4087 100644 --- a/src/chemistry.rs +++ b/src/chemistry.rs @@ -2146,6 +2146,7 @@ mod chem_tests { } #[test] + #[ignore] // It would be good to say "not chemistry" for this, but there aren't rules for that at the moment fn test_water_bond() -> Result<()> { return chem_test(|| { let test11 = r#"H2O=O"#; diff --git a/src/interface.rs b/src/interface.rs index 012345056..6486aa762 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -1373,11 +1373,11 @@ mod tests { set_mathml(good_mathml)?; let bad_mathml = "&xabc;"; assert!(set_mathml(bad_mathml).is_err()); - assert!(get_spoken_text()?.is_empty()); + assert!(get_spoken_text()? == ""); set_mathml(good_mathml)?; let bad_mathml = "garbage"; assert!(set_mathml(bad_mathml).is_err()); - assert!(get_spoken_text()?.is_empty()); + assert!(get_spoken_text()? == ""); return Ok( () ); }); } @@ -1404,7 +1404,7 @@ mod tests { x-y "; - set_mathml(expr)?; + set_mathml(&expr)?; let speech = get_spoken_text()?; // Rule-generated SSML must pass through verbatim (not XML-entity-encoded). assert!(!speech.contains("<")); diff --git a/src/navigate.rs b/src/navigate.rs index f893b3838..0ef0f62bc 100644 --- a/src/navigate.rs +++ b/src/navigate.rs @@ -1004,10 +1004,10 @@ mod tests { match do_navigate_command_string(mathml, command) { Err(e) => { panic!("\nStarting at '{}', '{} failed.\n{}", - start_id, command, crate::interface::errors_to_string(&e)) + start_id, command, &crate::interface::errors_to_string(&e)) }, Ok(nav_speech) => { - let nav_speech = nav_speech.trim_end_matches([' ', ',', ';']); + let nav_speech = nav_speech.trim_end_matches(&[' ', ',', ';']); // debug!("Full speech: {}", nav_speech); if !result_id.is_empty() { let (id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml); diff --git a/src/prefs.rs b/src/prefs.rs index 08f29f339..cad23ba6d 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -879,54 +879,6 @@ mod tests { return stripped_path } - struct TestRulesDir { - path: PathBuf, - } - - impl TestRulesDir { - fn path(&self) -> &Path { - return &self.path; - } - } - - impl Drop for TestRulesDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.path); - } - } - - fn copy_dir_recursive(from: &Path, to: &Path) { - std::fs::create_dir_all(to).unwrap(); - for entry in std::fs::read_dir(from).unwrap() { - let entry = entry.unwrap(); - let file_type = entry.file_type().unwrap(); - let target = to.join(entry.file_name()); - if file_type.is_dir() { - copy_dir_recursive(&entry.path(), &target); - } else { - std::fs::copy(entry.path(), target).unwrap(); - } - } - } - - fn test_rules_dir_with_prefs() -> TestRulesDir { - let path = std::env::temp_dir().join(format!("mathcat-prefs-test-{}", fastrand::u64(..))); - std::fs::create_dir_all(&path).unwrap(); - - let source_rules = abs_rules_dir_path(); - let prefs = std::fs::read_to_string(source_rules.join("prefs.yaml")).unwrap() - .replace("Language: Auto", "Language: zz"); - std::fs::write(path.join("prefs.yaml"), prefs).unwrap(); - std::fs::copy(source_rules.join("intent.yaml"), path.join("intent.yaml")).unwrap(); - std::fs::copy(source_rules.join("definitions.yaml"), path.join("definitions.yaml")).unwrap(); - - copy_dir_recursive(&source_rules.join("Languages").join("en"), &path.join("Languages").join("en")); - copy_dir_recursive(&source_rules.join("Languages").join("zz"), &path.join("Languages").join("zz")); - copy_dir_recursive(&source_rules.join("Braille").join("Nemeth"), &path.join("Braille").join("Nemeth")); - - return TestRulesDir { path }; - } - fn speech_rule_files_cache_is_empty() -> bool { crate::speech::SPEECH_RULES.with(|rules| rules.borrow().rule_files_cache_is_empty()) } @@ -1227,36 +1179,124 @@ cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] { }); } + /// #262: MathCAT must notice when a rule file on disk changes and reload it. + /// This copies the rule files it needs (`en`, `zz`, and `Nemeth`) into a uniquely-named + /// temp dir, so the test can freely modify files and run in parallel without touching the + /// real Rules dir or the user's prefs file. All global state is restored on the way out. #[test] fn test_up_to_date() { use std::fs; use std::thread::sleep; use std::time::Duration; + use crate::interface; + use tempfile::Builder; + + /// Recursively copy `src` into `dst` (creating `dst`). + fn copy_dir_all(src: &Path, dst: &Path) { + fs::create_dir_all(dst).unwrap(); + for entry in fs::read_dir(src).unwrap() { + let entry = entry.unwrap(); + let from = entry.path(); + let to = dst.join(entry.file_name()); + if from.is_dir() { + copy_dir_all(&from, &to); + } else { + fs::copy(&from, &to).unwrap(); + } + } + } + + // Restore all thread-local global state (rules dir, prefs, caches) when the test exits, + // even on a panic, so other tests sharing this thread are unaffected. + struct RestoreGuard; + impl Drop for RestoreGuard { + fn drop(&mut self) { + let _ = interface::set_preference("CheckRuleFiles", "Prefs"); + PREF_MANAGER.with(|pref_manager| { + let mut pref_manager = pref_manager.borrow_mut(); + // Force a full re-read: the stale temp paths would otherwise fool the up-to-date check. + pref_manager.sys_prefs_file = None; + pref_manager.user_prefs_file = None; + let _ = pref_manager.initialize(abs_rules_dir_path()); + }); + } + } + + let temp_dir = Builder::new() + .prefix("mathcat-up-to-date-") + .tempdir() + .expect("create temp rules dir"); + let temp_rules_dir = temp_dir.path(); + let real_rules_dir = abs_rules_dir_path(); + + // Only the "en" (fallback), "zz" (test language), and "Nemeth" (braille) rules are needed, + // plus the top-level files they include (incl. the full system prefs.yaml — partial YAML + // left keys like DecimalSeparator missing and crashed on CI with no user AppData prefs). + copy_dir_all(&real_rules_dir.join("Languages/en"), &temp_rules_dir.join("Languages/en")); + copy_dir_all(&real_rules_dir.join("Languages/zz"), &temp_rules_dir.join("Languages/zz")); + copy_dir_all(&real_rules_dir.join("Intent"), &temp_rules_dir.join("Intent")); + copy_dir_all(&real_rules_dir.join("Braille/Nemeth"), &temp_rules_dir.join("Braille/Nemeth")); + fs::copy(real_rules_dir.join("definitions.yaml"), temp_rules_dir.join("definitions.yaml")).unwrap(); + fs::copy(real_rules_dir.join("intent.yaml"), temp_rules_dir.join("intent.yaml")).unwrap(); + fs::copy(real_rules_dir.join("Braille/definitions.yaml"), temp_rules_dir.join("Braille/definitions.yaml")).unwrap(); + fs::copy(real_rules_dir.join("prefs.yaml"), temp_rules_dir.join("prefs.yaml")).unwrap(); + // Override only what this test needs (rest of prefs.yaml keeps defaults like DecimalSeparator). + let prefs_path = temp_rules_dir.join("prefs.yaml"); + let prefs_text = fs::read_to_string(&prefs_path).unwrap() + .replace("Language: Auto", "Language: zz"); + assert!(prefs_text.contains("Language: zz"), "failed to set Language: zz in copied prefs.yaml"); + fs::write(&prefs_path, prefs_text).unwrap(); + + interface::set_rules_dir(temp_rules_dir.to_str().unwrap()).unwrap(); + let _restore = RestoreGuard; - let test_rules = test_rules_dir_with_prefs(); - let prefs_file = test_rules.path().join("prefs.yaml"); - - let mut pref_manager = PreferenceManager::default(); - pref_manager.set_rules_dir(test_rules.path()).unwrap(); - pref_manager.user_prefs_file = Some(crate::speech::FileAndTime::new_with_time(test_rules.path().join("no-user-prefs.yaml"))); - pref_manager.set_preference_files().unwrap(); - pref_manager.api_prefs.prefs.insert("Language".to_string(), Yaml::String("zz".to_string())); - pref_manager.api_prefs.prefs.insert("SpeechStyle".to_string(), Yaml::String("ClearSpeak".to_string())); - assert_eq!(&pref_manager.pref_to_string("Language"), "zz"); - pref_manager.set_all_files(test_rules.path()).unwrap(); - - assert_eq!(&pref_manager.pref_to_string("SpeechStyle"), "ClearSpeak"); - assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/ClearSpeak_Rules.yaml")); - - let changed_contents = fs::read_to_string(&prefs_file).unwrap() - .replace("SpeechStyle: ClearSpeak", "SpeechStyle: SimpleSpeak"); - fs::write(&prefs_file, changed_contents).unwrap(); - sleep(Duration::from_millis(5)); // make sure the time changes enough to be recognized - - pref_manager.set_preference_files().unwrap(); - pref_manager.api_prefs.prefs.remove("SpeechStyle"); - assert_eq!(&pref_manager.pref_to_string("SpeechStyle"), "SimpleSpeak"); - assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/SimpleSpeak_Rules.yaml")); + // Pin in memory so a user prefs.yaml in the config dir cannot override Language/SpeechStyle + // (and so SpeechStyle stays ClearSpeak until we change it for the reload check). + PREF_MANAGER.with(|pref_manager| { + let mut pref_manager = pref_manager.borrow_mut(); + pref_manager.set_string_pref("Language", "zz").unwrap(); + pref_manager.set_string_pref("SpeechStyle", "ClearSpeak").unwrap(); + pref_manager.set_string_pref("BrailleCode", "Nemeth").unwrap(); + assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/ClearSpeak_Rules.yaml")); + assert_ne!(&pref_manager.pref_to_string("DecimalSeparator"), NO_PREFERENCE, + "DecimalSeparator missing — system prefs.yaml incomplete"); + }); + // "All" makes MathCAT check rule-file timestamps (not just prefs), so content edits are noticed. + interface::set_preference("CheckRuleFiles", "All").unwrap(); + + // ---- speech rule file: change its content and confirm the new speech is used ---- + interface::set_mathml("+10").unwrap(); + assert_eq!(interface::get_spoken_text().unwrap(), "ClearSpeak positive from zz 10"); + + let speech_rule_file = temp_rules_dir.join("Languages/zz/ClearSpeak_Rules.yaml"); + let speech_contents = fs::read_to_string(&speech_rule_file).unwrap(); + fs::write(&speech_rule_file, speech_contents.replace("ClearSpeak positive from zz", "changed positive from zz")).unwrap(); + sleep(Duration::from_millis(5)); // make sure the modification time changes enough to be recognized + assert_eq!(interface::get_spoken_text().unwrap(), "changed positive from zz 10", + "modified speech rule file was not reloaded"); + + // ---- changing SpeechStyle must not change the language (still "zz") ---- + interface::set_preference("SpeechStyle", "SimpleSpeak").unwrap(); + PREF_MANAGER.with(|pref_manager| { + let pref_manager = pref_manager.borrow(); + assert_eq!(&pref_manager.pref_to_string("Language"), "zz", "changing SpeechStyle changed the language"); + assert_eq!(rel_path(&pref_manager.rules_dir, pref_manager.speech.as_path()), PathBuf::from("Languages/zz/SimpleSpeak_Rules.yaml")); + }); + // the (unmodified) zz SimpleSpeak rules produce their own distinctive text + assert_eq!(interface::get_spoken_text().unwrap(), "SimpleSpeak positive from zz 10"); + + // ---- braille (Nemeth) unicode file: change a char and confirm the new braille is used ---- + let braille_before = interface::get_braille("").unwrap(); + let unicode_file = temp_rules_dir.join("Braille/Nemeth/unicode.yaml"); + let unicode_contents = fs::read_to_string(&unicode_file).unwrap(); + // remap the digit '1' to the (distinctive) full braille cell '⠿' + let changed_unicode = unicode_contents.replace("\"1\": [t: \"N⠂\"]", "\"1\": [t: \"N⠿\"]"); + assert_ne!(changed_unicode, unicode_contents, "expected to find the Nemeth mapping for '1'"); + fs::write(&unicode_file, changed_unicode).unwrap(); + sleep(Duration::from_millis(5)); + let braille_after = interface::get_braille("").unwrap(); + assert_ne!(braille_after, braille_before, "modified Nemeth unicode file was not reloaded"); + assert!(braille_after.contains('⠿'), "new Nemeth mapping for '1' not reflected in braille: {braille_after}"); } }} diff --git a/src/speech.rs b/src/speech.rs index 1c475e430..960b105e3 100644 --- a/src/speech.rs +++ b/src/speech.rs @@ -3016,8 +3016,7 @@ cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] { let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone(); // open the file, read all the contents, then write them back so the time changes - let contents = std::fs::read(&start_main_file.file) - .unwrap_or_else(|_| panic!("Failed to read file {} during test", start_main_file.file.to_string_lossy())); + let contents = std::fs::read(&start_main_file.file).unwrap_or_else(|_| panic!("Failed to read file {} during test", start_main_file.file.to_string_lossy())); std::fs::write(start_main_file.file, contents).unwrap(); std::thread::sleep(Duration::from_millis(5)); // pause a little to make sure the time changes From 8f3ce9d7ab292c4c5b21f869ef255efc520596a6 Mon Sep 17 00:00:00 2001 From: Danil <81031453+Kostenkov-2021@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:44:00 +0500 Subject: [PATCH 3/3] Ru - handle lone log/ln; suppress nav announcements (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ru: handle lone log/ln; suppress nav announcements Add handling for lone mi elements 'log' and 'ln' in ClearSpeak (supports Verbosity and ClearSpeak_Log LnAsNaturalLog option). Add a navigation rule to avoid announcing "in/out of" for prefix or silent intents that have no NavigationParts. Include unit tests: new ClearSpeak tests for individual log/ln, new navigate tests for suppressed base announcements, and register the navigate test module. * Russian braille: bracket/arrow mappings & tests Add contextual bracket-recognition rules (square and angle) to Rules/Braille/Russian/Russian_Rules.yaml and add Unicode braille mappings for ≶, ≷, ⟨, ⟩, ↑, ↓ in Rules/Braille/Russian/unicode.yaml. Extend tests in tests/braille/Russian/russian.rs to cover Russian Braille GOST 2019 order relations, large vertical arrows, and contextual bracket recognition for square and angle brackets, validating the expected braille output. --- Rules/Braille/Russian/Russian_Rules.yaml | 26 ++++++++++ Rules/Braille/Russian/unicode.yaml | 6 +++ Rules/Languages/ru/ClearSpeak_Rules.yaml | 18 +++++++ Rules/Languages/ru/navigate.yaml | 9 ++++ tests/Languages/ru.rs | 3 +- tests/Languages/ru/ClearSpeak/functions.rs | 16 +++++++ tests/Languages/ru/navigate.rs | 56 ++++++++++++++++++++++ tests/braille/Russian/russian.rs | 29 +++++++++++ 8 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 tests/Languages/ru/navigate.rs diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml index ee9004aed..1bf1603c9 100644 --- a/Rules/Braille/Russian/Russian_Rules.yaml +++ b/Rules/Braille/Russian/Russian_Rules.yaml @@ -1555,6 +1555,32 @@ replace: - x: "DefinitionValue(., 'Braille', 'RussianFunctionBraille')" +- + name: contextual-square-bracket-recognition-point + tag: mo + match: + - "parent::*[IsBracketed(., '[', ']')] and" + - "((.='[' and not(preceding-sibling::*) and following-sibling::*[1][self::m:mi or self::m:mtext]) or" + - " (.=']' and not(following-sibling::*) and preceding-sibling::*[1][self::m:mi or self::m:mtext]))" + replace: + - T: "⠠" + - x: "text()" + +- + name: contextual-angle-bracket-recognition-point + tag: mo + match: + - "parent::*[IsBracketed(., '⟨', '⟩')] and" + - "((.='⟨' and not(preceding-sibling::*)) or (.='⟩' and not(following-sibling::*))) and" + - "(parent::*/*[2][self::m:mi or self::m:mtext] or" + - " parent::*/*[last()-1][self::m:mi or self::m:mtext])" + replace: + - T: "⠈" + - test: + - if: ".='⟨'" + then: [T: "⠪"] + else: [T: "⠕"] + - name: div-operator-function tag: mo diff --git a/Rules/Braille/Russian/unicode.yaml b/Rules/Braille/Russian/unicode.yaml index 6b08895b9..4fc43ea9d 100644 --- a/Rules/Braille/Russian/unicode.yaml +++ b/Rules/Braille/Russian/unicode.yaml @@ -214,6 +214,8 @@ - "≢": [t: "⠀⠰⠾"] - "≤": [t: "⠀⠪⠶"] - "≥": [t: "⠀⠕⠶"] +- "≶": [T: "⠀⠪⠕⠀"] +- "≷": [T: "⠀⠕⠪⠀"] - "≪": [t: "⠀⠪⠪⠀"] - "≫": [t: "⠀⠕⠕⠀"] - "≺": [t: "⠀⠒⠪⠀"] @@ -238,6 +240,10 @@ - "⋰": [t: "⠠⠲⠲⠲"] - "⋱": [t: "⠠⠲⠲⠲"] - "△": [t: "⠸⠙"] +- "⟨": [T: "⠪"] +- "⟩": [T: "⠕"] +- "↑": [T: "⠰⠌"] +- "↓": [T: "⠘⠡"] - "→": [t: "⠀⠒⠕"] - "⟶": [t: "⠀⠒⠕"] - "←": [t: "⠀⠦⠶"] diff --git a/Rules/Languages/ru/ClearSpeak_Rules.yaml b/Rules/Languages/ru/ClearSpeak_Rules.yaml index c0955204c..0815c96c5 100644 --- a/Rules/Languages/ru/ClearSpeak_Rules.yaml +++ b/Rules/Languages/ru/ClearSpeak_Rules.yaml @@ -760,6 +760,24 @@ - pause: short - x: "*[3]" +# Handle log and ln when they remain individual mi elements rather than the +# canonical three-child function-application mrow handled by the next rule. +- name: ClearSpeak-log + tag: mi + match: ".='log' or .='ln'" + replace: + - bookmark: "@id" + - test: + if: "$Verbosity!='Terse' and following-sibling::*[2] and + not(IsNode(following-sibling::*[2],'simple'))" + then: [T: "от"] + - test: + - if: ".='log'" + then: [T: "логарифм"] + - else_if: "$ClearSpeak_Log = 'LnAsNaturalLog'" + then: [T: "натуральный логарифм"] + else: [T: "эл эн"] + - name: ClearSpeak-log tag: mrow variables: [{log_is_simple: "IsNode(*[3],'simple')"}] diff --git a/Rules/Languages/ru/navigate.yaml b/Rules/Languages/ru/navigate.yaml index 527dcce60..01bc51a76 100644 --- a/Rules/Languages/ru/navigate.yaml +++ b/Rules/Languages/ru/navigate.yaml @@ -91,6 +91,15 @@ # Rules for speaking what happens when moving into or out of a notation +# Prefix/silent intents with no NavigationParts entry (for example, unary +# minus, a vector, or a modified variable) should not announce "in"/"out of". +- name: into-or-out-of-prefix-or-silent-without-parts + tag: "*" + match: "$Move2D != '' and + (contains(@data-intent-property, ':prefix:') or contains(@data-intent-property, ':silent:')) and + GetNavigationPartName(name(.), count($Child2D/preceding-sibling::*)) = ''" + replace: [] + - name: into-or-out-of-mfrac tag: "*" match: "$Move2D != '' and (@data-from-mathml='mfrac' or self::m:mfrac)" diff --git a/tests/Languages/ru.rs b/tests/Languages/ru.rs index 397e29a08..97edff4ee 100644 --- a/tests/Languages/ru.rs +++ b/tests/Languages/ru.rs @@ -31,4 +31,5 @@ mod units; mod chemistry; mod alphabets; mod intent; -mod mtable; \ No newline at end of file +mod mtable; +mod navigate; diff --git a/tests/Languages/ru/ClearSpeak/functions.rs b/tests/Languages/ru/ClearSpeak/functions.rs index 941e694d8..5171150f6 100644 --- a/tests/Languages/ru/ClearSpeak/functions.rs +++ b/tests/Languages/ru/ClearSpeak/functions.rs @@ -144,6 +144,22 @@ fn natural_log() -> Result<()> { return Ok(()); } +#[test] +fn individual_log_mi() -> Result<()> { + let expr = "log"; + test("ru", "ClearSpeak", expr, "логарифм")?; + return Ok(()); +} + +#[test] +fn individual_ln_mi() -> Result<()> { + let expr = "ln"; + test("ru", "ClearSpeak", expr, "эл эн")?; + test_ClearSpeak("ru", "ClearSpeak_Log", "LnAsNaturalLog", expr, + "натуральный логарифм")?; + return Ok(()); +} + #[test] fn explicit_function_call_with_parens() -> Result<()> { let expr = "t(x)"; diff --git a/tests/Languages/ru/navigate.rs b/tests/Languages/ru/navigate.rs new file mode 100644 index 000000000..690df9eff --- /dev/null +++ b/tests/Languages/ru/navigate.rs @@ -0,0 +1,56 @@ +//! Russian navigation speech for prefix/silent intents without NavigationParts. + +use crate::common::*; +use anyhow::Result; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn init_nav(mathml: &str) -> Result<()> { + set_rules_dir(abs_rules_dir_path())?; + set_preference("Language", "ru")?; + set_preference("SpeechStyle", "SimpleSpeak")?; + set_preference("Verbosity", "Medium")?; + set_preference("NavMode", "Enhanced")?; + set_preference("NavVerbosity", "Verbose")?; + set_preference("AutoZoomOut", "False")?; + set_preference("Overview", "False")?; + set_mathml(mathml)?; + Ok(()) +} + +fn assert_zoom_in(mathml: &str, expected: &str) -> Result<()> { + init_panic_handler(); + let result = catch_unwind(AssertUnwindSafe(|| { + init_nav(mathml)?; + let speech = do_navigate_command("ZoomIn")?; + let trimmed_speech = speech.trim_end_matches([' ', ',', ';']).to_string(); + assert_eq!(expected, trimmed_speech); + Ok(()) + })); + report_any_panic(result) +} + +#[test] +fn no_parts_prefix_vector_suppresses_base_announcement() -> Result<()> { + let expr = r#" + + + v + + + + "#; + assert_zoom_in(expr, "переход внутрь; вэ") +} + +#[test] +fn no_parts_silent_modified_variable_suppresses_base_announcement() -> Result<()> { + let expr = r#" + + + x + ^ + + + "#; + assert_zoom_in(expr, "переход внутрь; икс") +} diff --git a/tests/braille/Russian/russian.rs b/tests/braille/Russian/russian.rs index 5138fb374..1b40f13b7 100644 --- a/tests/braille/Russian/russian.rs +++ b/tests/braille/Russian/russian.rs @@ -1387,6 +1387,35 @@ fn source_less_common_math_symbols_11() -> Result<()> { return test_russian_braille(r#"ab"#, "⠠⠁⠀⠼⠀⠃"); } +#[test] +fn source_gost_order_relations_greater_or_less() -> Result<()> { + return test_russian_braille(r#"xy"#, "⠠⠭⠀⠕⠪⠀⠽"); +} + +#[test] +fn source_gost_order_relations_less_or_greater() -> Result<()> { + return test_russian_braille(r#"xy"#, "⠠⠭⠀⠪⠕⠀⠽"); +} + +#[test] +fn source_gost_large_vertical_arrows() -> Result<()> { + test_russian_braille(r#""#, "⠰⠌")?; + return test_russian_braille(r#""#, "⠘⠡"); +} + +#[test] +fn source_gost_contextual_square_bracket_recognition_points() -> Result<()> { + test_russian_braille(r#"[x]"#, "⠠⠷⠠⠭⠠⠾")?; + return test_russian_braille(r#"[1]"#, "⠷⠼⠁⠾"); +} + +#[test] +fn source_gost_contextual_angle_bracket_recognition_points() -> Result<()> { + test_russian_braille(r#"x"#, "⠈⠪⠠⠭⠈⠕")?; + test_russian_braille(r#"1"#, "⠪⠼⠁⠕")?; + return test_russian_braille(r#"x<y"#, "⠠⠭⠀⠪⠀⠽"); +} + #[test] fn source_typeform_and_mathvariant_indicators() -> Result<()> { let expr = r#"x"#;