Skip to content

Deprecate the MECAB magic word in LgRegexpWordCharacters #288

Open

Description

@HugoFara

LgRegexpWordCharacters = 'MECAB' is a magic string: the field that should hold a word-characters regex instead holds a literal marker meaning "tokenize with MeCab". LgParserType now exists to say that properly, so the magic word should go — but it is not a one-line swap, because it carries a second fact that LgParserType does not.

It means two different things

Ten PHP sites read it, and they do not agree on what it is.

As a parser selector — genuinely superseded by LgParserType:

  • Language::getEffectiveParserType() (Language.php:434)
  • ParserRegistry::resolveParserType() / resolveParserTypeFromRow() (ParserRegistry.php:199,228)
  • ExternalParser.php:551

As "this language has no spaces between words" — not superseded, and this is the blocker. In SentenceService it appears in six places alongside LgSplitEachChar and LgRemoveSpaces, governing how spaces are reconstructed around tokens:

// SentenceService.php:333, 362 (and 136, 290, 455, 548)
if (!$removeSpaces && !($splitEachChar || 'MECAB' == strtoupper(trim($termchar)))) {

Setting LgParserType = 'mecab' and clearing the field would silently change Japanese spacing, because nothing else tells SentenceService the language is space-free. That fact needs an explicit home first — LgRemoveSpaces already exists, or it could be derived from the parser's own capabilities.

It also consumes the field it occupies: a MeCab language has no word-characters regex at all, which interacts badly with anything reasoning about word characters (see #278).

It is still a live input path

Not a legacy artifact to read-only-support. The word-characters selector still offers "MeCab (recommended)" (locale/en/language.json → form.regexp_alt_mecab), and language_form.ts::wordCharChange() writes the literal string into the field. New magic-word languages are still being created today.

Meanwhile the modern path has already moved on without it: langdefs.json's Japanese preset declares "parserType": "mecab", and #281 is what makes that preset take effect.

A bug it has already caused

Every reader normalizes before comparing — strtoupper(trim($x)) === 'MECAB' — except one:

// GetPhoneticReading.php:54
if ($wordCharacters !== "mecab") {
    return $text;
}

A language whose field holds MECAB in any other casing (an old install, an import, a hand-edit) silently gets no phonetic reading: no error, just the input back. Worth fixing on its own, independently of this issue.

Why it matters beyond tidiness

Parser identity currently lives in three places — the magic word, LgSplitEachChar, and LgParserType — and 20251223_120000_add_parser_type.sql backfills the third from the first two. That ambiguity is what made #281 change tokenization for every CJK language on an upgraded install: it started trusting a column holding derived values. The fix there (17262f5) reads a type that merely restates its neighbouring flag as the flag rather than as intent — correct for now, and a workaround for this issue. Retiring the magic word is what would let LgParserType simply mean what the user chose, and let that workaround be deleted.

Suggested plan

  1. Give "no spaces between words" an explicit home, so SentenceService stops asking about the magic word.
  2. Migration: for UPPER(TRIM(LgRegexpWordCharacters)) = 'MECAB', set LgParserType = 'mecab' (the backfill already did), set the space flag, and put a real regex (or empty) back in the word-characters field.
  3. Drop the MeCab option from the word-characters selector, so nothing new is written; the parser dropdown already covers it.
  4. Keep the readers accepting the magic word for one release, logging a deprecation.
  5. Next release: delete the readers, and with them the restatesALegacySignal() workaround in ParserRegistry.

Roughly 10 PHP sites, 2 TS files, 1 migration, 1 locale key. Small, but it changes how Japanese renders, so it wants a before/after parse comparison across every language — the same one that caught the #281 regression.

Activity

  1. HugoFara commented on Sep 4, 2026

    @HugoFara
    OwnerAuthor

    Status against the plan above: steps 1–4 shipped in 3.7.0. Step 5 is a 3.8.0 item, and deliberately so.

    Step State Where
    1. Give "no spaces between words" an explicit home done WordSpacing (a57b5f2) — SentenceService's six hand-written comparisons now call separatesWordsWithSpaces()
    2. Migration: set LgParserType/LgRemoveSpaces, restore a real regex done 20260831_120000_retire_mecab_magic_word.sql (df49b12)
    3. Drop MeCab from the word-characters selector done form.regexp_alt_mecab and language_form.ts::wordCharChange() both gone (a57b5f2)
    4. Keep the readers, logging a deprecation done WordSpacing::noteDeprecatedUse() → error_log, deduplicated per request, plus the admin server data panel (c357e32)
    5. Delete the readers and the restatesALegacySignal() workaround open — 3.8.0 see below

    The casing bug called out above is fixed too (a57b5f2): GetPhoneticReading compared against lowercase "mecab" without normalising, so a field holding MECAB in any other casing silently returned the input unchanged. It resolves through getEffectiveParserType() !== ParserSelection::MECAB now.

    Why step 5 waits rather than lands now

    Step 4 exists to give installs one release to migrate, and the notice it logs says so in as many words — "Support for the marker is removed in a future release (see issue #288)". Deleting the readers in the same release that started warning would make that promise false for anyone who upgrades across both at once. So this stays open until 3.8.0 rather than being closed as substantially done.

    What 3.8.0 deletes

    • WordSpacing::usesMecabMagicWord() and the MECAB_MAGIC_WORD constant
    • its three callers: Language::getEffectiveParserType(), ParserRegistry::resolveParserType() / resolveParserTypeFromRow(), and ExternalParser.php:552
    • ParserRegistry::restatesALegacySignal() — the fix(language): make the parser setting real, and Chinese use jieba (#278) #281 workaround. This is the prize: it exists because LgParserType cannot yet be trusted to mean what the user chose rather than a restatement of a derived neighbouring flag. Retiring the marker is what lets that column simply mean intent.
    • MySqlLanguageRepository::findUsingDeprecatedMecabMarker() and the Maintenance.php query
    • the deprecated_mecab_languages panel in server data, with its two locale keys

    Two cautions when picking it up

    Order matters at the end. The server-data panel is what tells an admin whether their install still has affected rows, so it is the last thing removed, not the first — pulling it early removes the evidence that the readers are still needed.

    It changes how Japanese renders. Per the closing note above, this wants the before/after parse comparison across every language — the same one that caught the #281 regression — not just a green test suite.


    No GitHub milestone set: this repo only has the dormant "LWT 3" milestone and none of the current issues use per-version ones, so this note is the record rather than inventing a convention. Happy to create a 3.8.0 milestone if you'd rather track it that way.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    back-endBack-end related issueenhancementDevelop an existing feature

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions