@shotlingo/character-counter
v0.1.0
Published
Count text the way App Store Connect and Google Play Console count it (UTF-16 code units), plus human-perceived grapheme count and UTF-8 byte length. Zero dependencies, no network calls.
Maintainers
Readme
@shotlingo/character-counter
Count text the way App Store Connect and Google Play Console count it (UTF-16 code units), plus the count a human would actually perceive (grapheme clusters) and UTF-8 byte length. Zero dependencies, no network calls.
Field limits to check against: @shotlingo/character-limits Full interactive reference: shotlingo.com/tools/app-store-character-limits
Why this matters
App Store Connect and Play Console both count length in UTF-16 code units, the same value as JavaScript's string.length. A single flag emoji or an accented character built from a surrogate pair can silently eat 2 or more of your 30-character subtitle. Array.from(text).length does not fix this either: it counts Unicode codepoints, not the code units the stores actually enforce, and it still splits multi-codepoint grapheme clusters like flags and family emoji into more than one unit.
This package gives you all three counts so you can check the one that matches the field you are filling in.
Install
npm install @shotlingo/character-counterUsage
import { countCharacters, countGraphemes, byteLength, countAll, checkAgainstLimit } from '@shotlingo/character-counter';
countCharacters('🇹🇷');
// => 4 (what App Store Connect / Play Console count)
countGraphemes('🇹🇷');
// => 1 (what a person sees)
byteLength('İstanbul');
// => 9 (UTF-8 bytes; countCharacters would say 8)
countAll('🇹🇷');
// => { characters: 4, graphemes: 1, bytes: 8 }
checkAgainstLimit('My Great App', 30);
// => { count: 12, limit: 30, unit: 'characters', fits: true, remaining: 18 }API
countCharacters(text: string): number— UTF-16 code unit count. Matchesstring.lengthand store character counters.countGraphemes(text: string): number— user-perceived character count viaIntl.Segmenter.byteLength(text: string): number— UTF-8 byte length.countAll(text: string): CountResult—{ characters, graphemes, bytes }.checkAgainstLimit(text: string, limit: number, unit?: 'characters' | 'bytes'): LimitCheck—{ count, limit, unit, fits, remaining }. Defaults to'characters', matching how stores enforce their limits.
License
MIT
