Lesstep
Japanese text · length limits · Checked October 29, 2026

Length limits that break in Japanese

Japanese is written without spaces, and most Japanese characters take three bytes in UTF-8. Code that cuts a summary at the last space, measures a limit in bytes, or counts words by splitting on spaces works in English and goes wrong in Japanese. Count and cut by characters, as a person sees them:

const seg = new Intl.Segmenter(
  "ja", { granularity: "grapheme" });
const BREAK = /[\s。、!?」]/;

function cut(text, max) {
  const cs = [...seg.segment(text)]
    .map((s) => s.segment);
  if (cs.length <= max) return text;
  let end = max - 1;
  // End after 。 or 、 if one is near
  const near = max * 0.8;
  for (let i = end; i > near; i--) {
    if (BREAK.test(cs[i - 1])) {
      end = i;
      break;
    }
  }
  return cs.slice(0, end).join("")
    .trimEnd() + "…";
}

cut() counts what a person sees as one character (a grapheme), so it never splits a character in two. The "…" counts toward the limit, and the cut falls after a punctuation mark or a space when one is in the last fifth. We ran it at every limit on three texts that mix Japanese, English and emoji, 212 cuts in all: none went over the limit, and none broke a character.

Cutting at the last space

calcom/cal.diy #29841: Cal.diy, an open-source scheduling app with over 49,000 stars on GitHub, shortens the descriptions in its link previews with truncateOnWord. It takes the first 148 characters and cuts them back to the last space. Japanese has none, so lastIndexOf(" ") returns -1 and the function returns "..." alone. We ran the function as the issue quotes it: a 308-character Japanese description came back as .... With one English sentence in front, it came back as that sentence alone. On October 29, 2026, the code was unchanged in the main branch, and none of the pull requests with a fix had been merged.

What counts as one character

Text.lengthlen()seenbytes
請求書3339
𠮷田3227
葛󠄀3217
ガ2216
👍🏽4218

.length is JavaScript's, len() is Python's, "seen" is what a person counts, and bytes are UTF-8. 請求書 means "invoice"; 𠮷田 is a family name whose first kanji takes two code units in JavaScript; 葛󠄀 is 葛 with a variation selector, which picks one of its printed forms, as some names use; ガ is the half-width form of ガ, two code points.

HTML's maxlength counts JavaScript's length: the number of characters "is measured using length" (the HTML standard), and a string's length is "the number of code units it contains" (the Infra standard). Python's len() counts code points, and so does PostgreSQL's varchar(n), "n characters (not bytes)" (its docs). In Chromium 152, we typed into fields with maxlength:

Show people your own count, [...seg.segment(text)].length, and check the same count on the server. Cutting with slice() can split a character: "𠮷田".slice(0, 1) is half of 𠮷. It shows as �, encodeURIComponent throws "URIError: URI malformed", and TextEncoder encodes it as U+FFFD.

Limits counted in bytes

A limit counted in UTF-8 bytes holds a third as many Japanese characters: 256 bytes held 85 characters of our text. Silo #2867, a media server: Discord counts its embed limits in characters, Silo counted bytes, so a Japanese title is cut at about 85 characters of the 256 Discord allows. Some limits really are bytes: an Amazon S3 object key is at most 1,024 bytes (AWS's docs), 341 Japanese characters. Cutting the bytes themselves breaks the last character: Buffer.from("請求書").subarray(0, 4) decodes as 請 and �. Fit a byte limit one whole character at a time:

const enc = new TextEncoder();

function fitBytes(text, limit) {
  let out = "";
  let used = 0;
  for (const { segment } of
       seg.segment(text)) {
    used += enc.encode(segment).length;
    if (used > limit) break;
    out += segment;
  }
  return out;
}

fitBytes("請求書を送りました", 10) returns 請求書, 9 bytes, since the next character would make 12. We ran it at every byte limit on the same three texts, 612 in all: none went over, and none ended inside a character.

Word counts and reading time

Splitting on spaces finds one word in 請求書を送りました。明日までにご確認ください。 ("We sent the invoice. Please check it by tomorrow."). Japanese length is counted in characters. Kavita #4805, a reading server: word counts for Chinese, Japanese and Korean books come out far too low, because it divides the number of letters by five, an English average, and its reading times are off by the same factor. Medium times stories in Chinese, Japanese and Korean by characters, 500 a minute, against roughly 265 words a minute for other posts (its help center):

function readMinutes(text, lang) {
  const isJa = lang === "ja";
  const n = isJa
    ? text.replace(/\s/g, "").length
    : text.trim().split(/\s+/).length;
  return Math.max(1,
    Math.round(n / (isJa ? 500 : 265)));
}

A 3,080-character Japanese text came out at 6 minutes; counted as English words, at 1.

Some limits count Japanese as two

X counts most characters as one, but Chinese, Japanese and Korean characters and emoji as two, so a post holds 140 Japanese characters, not 280 (its docs on counting characters). If your app writes posts for X, count them the way X's docs advise, with its open-source twitter-text library; we did not run it for this page.

The rest of a Japanese check

A link preview that says only "..." shows up wherever your link is shared. The same pass catches Enter while typing Japanese (the fix), yen charged 100 times over (the fix), kanji drawn in their Chinese shapes (the fix), headings that break in the middle of a word (the fix), postal codes rejected when typed in Japanese (the fix), dates saved one day early in Japan (the fix), address forms in the American order (the fix), phone numbers sent to +81 with the 0 still in (the fix), PDFs with the Japanese missing (the fix), search that finds nothing in Japanese (the fix), lists sorted in the wrong order (the fix), email subjects that arrive garbled (the fix), CSV files that open garbled in Excel (the fix), Japanese titles that give an empty URL slug (the fix), file names garbled on upload and download (the fix), dates in the Japanese era that come out wrong (the fix), full-width spaces that get past your checks (the fix), public holidays your app gets wrong (the fix), and vertical text that comes out sideways or upside down (the fix).

Lesstep translates web apps into Japanese and delivers them as one pull request. In Complete (US$249), we check every screen in Japanese, previews, counters and length limits included, and tell you where Japanese gets cut short. The first step is a free sample.

Cal.com is a trademark of Cal.com, Inc.; Discord is a trademark of Discord Inc.; Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates; X is a trademark of X Corp.; Medium is a trademark of A Medium Corporation; Python is a registered trademark of the Python Software Foundation; PostgreSQL is a registered trademark of the PostgreSQL Community Association; Node.js is a trademark of the OpenJS Foundation. This page is not affiliated with or endorsed by any of them.

More: Enter sends the message while typing Japanese · Stripe charges 100 times too much in yen · Kanji in their Chinese shapes · Japanese headings that break in the middle of a word · Forms that reject numbers typed in Japanese · Dates that come out wrong for Japanese users · Address forms in the Japanese order · Japanese phone numbers in forms · Japanese text missing from your PDFs · Search that finds nothing in Japanese · Japanese lists sorted in the wrong order · Japanese email subjects that arrive garbled · Japanese CSV files that open garbled in Excel · Japanese titles that give an empty URL slug · Japanese file names garbled on upload and download · Japanese era dates that come out wrong · Full-width spaces that get past your checks · Japanese public holidays your app gets wrong · Japanese vertical text that comes out sideways or upside down · Japanese localization checklist · Japanese localization for your web app