openlang

UTF-16 length or grapheme count for a one-character limit

An editor suggestion rewrote text.length <= 1 as a grapheme-count check on a label field. Bug fix or changed requirement?

UTF-16 lengths for A, é and 🇰🇷 are 1, 2 and 4, with é written as e plus U+0301. All three are one grapheme. So the old check accepts only A and the new one accepts all three.

A string type can't choose the counting rule. The test has to commit to UTF-16 units or graphemes, and the field's wording has to match.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    Then the test has a fixed point either way. If the rule limits UTF-16 units, é has to keep failing the one-unit check. If it limits graphemes, é has to pass.

  2. 固定链接

    Okay, the helper text says “one character.” I paste é, submit, and the field says it's too long. Running it shows how the unit check got there. Whether that message keeps the product's promise is something the run can't tell me.

    Pick the wording and the rule together. If the limit means user-perceived characters, write down which grapheme segmentation rule is in use and make the test expect all three examples to pass. Default segmentation can be tailored to a language or context, so “what people see” needs a stated reading too. If it's really about storage, the help text should just say storage.

    Small aside. The submitted state could carry the count and its unit, so the error branch describes the check that actually ran. Naming that state in TypeScript keeps the UI in step with the decision.

  3. 固定链接

    Put the three examples through a default Intl.Segmenter, which means grapheme granularity.

    const seg = new Intl.Segmenter();
    for (const s of ["A", "e\u0301", "🇰🇷"]) {
      console.log(s.length, [...seg.segment(s)].length);
    }

    It prints 1 1, 2 1 and 4 1, so counting segments matches the helper text that says "one character". A family emoji glued together with zero-width joiners is one segment too. The field counts segments now and the test lists all four as passing.

    Then I saved one and the policy check on the server refused three of the four. That rule is Rego, count(input.label) > 1, and count on a string counts code points. There e plus U+0301 is two, the flag is two, the family is several. So the browser and the policy disagree about what one character is, and the error people see comes from the side I hadn't touched. Next is getting both sides onto one rule, and I'm not sure Rego has a grapheme count at all.

同一个问题的其他角度

  1. Is an NFC key a sensible default for name lookup?

    Okay, a user pastes their name and it ends in e followed by U+0301. Later they type the same name and the keyboard gives U+00…

    回复 6最近活动 typescriptjavascript
  2. Mode line columns drift as soon as a buffer name has Hangul in it

    Padded the buffer name in my mode line to a fixed width so the bits after it line up, and it worked for a long while (well, i…

    回复 1最近活动 emacs-lisp
  3. Truncating by bytes like the old C++, and Rust panics

    Porting a chunk of old C++ to Rust that cuts a std::string down to a byte limit with a plain s.substr(0, n), and I figured it…

    回复 6最近活动 rustcpp