RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-07-15Advanced

The Comma That Fell to the Start of a Line: Rork, Quote Cards, and Japanese Line Breaking

React Native's Text component does not guarantee Japanese line-breaking rules. Here are the violation rates I measured, a WORD JOINER implementation that survives copy and VoiceOver, an onTextLayout audit harness, and how Rork Max (SwiftUI) differs.

Rork547React Native234Expo193JapaneseTypographyText

Premium Article

I picked up the iPhone SE and stopped halfway through the screen.

A quote card in one of my wallpaper apps. The second line began with a Japanese comma — the punctuation had fallen to the start of the line, which Japanese typography does not allow.

Rork had generated the screen from a plain request: show the quote large and centered. The code worked. The layout held. It simply was not set as Japanese text.

The iPhone 15 Pro simulator never showed it. Different width, different wrap points. I had only been lucky.

React Native's Text does not guarantee line-breaking rules

React Native's Text does not implement line breaking itself. On iOS it hands off to TextKit / CoreText. On Android it hands off to android.text.LineBreaker.

Wrap quality therefore depends on the platform and its locale resolution. That turned out to be the hard part.

EnvironmentLine-start rulesWhat I observed
iOS (default)CoreText applies some rulesCommas and periods mostly avoided. Prolonged sound mark and small kana still fall to line start
iOS with lineBreakStrategyIOS="push-out"Pushes the previous character downViolations drop sharply, but line count can grow
Android (default simple)Essentially noneCommas, periods, and closing brackets land at line start
Android with textBreakStrategy="balanced"Evens out line lengthsLooks tidier, but the rules themselves are not guaranteed

I had assumed Android would apply Japanese rules whenever the device was set to Japanese. In practice the decision comes from the locale attached to the text, not from the user's system language.

My apps ship in 16 languages. A reader looking at Japanese quotes may well have an English device. I had missed that premise entirely.

I measured the violation rate first

Fixing by feel produces the feeling of a fix and nothing else. So I took numbers.

The sample: 120 quote strings used across six wallpaper apps, including the calm and affirmation titles. Four container widths: 320, 375, 393, and 430.

onTextLayout returns each line after layout resolves. If you can read the first character of a line, you can decide the violation directly.

// KinsokuAudit.tsx — detect line-start violations at runtime
import { Text, type TextLayoutEventData, type NativeSyntheticEvent } from "react-native";
 
// Characters that must not start a line: punctuation, small kana, prolonged sound mark
const LINE_START_FORBIDDEN =
  "、。,.・:;?!ヽヾゝゞ々ー’”)〕]}〉》」』】…‥" +
  "ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ";
 
type Violation = { lineIndex: number; head: string; lineText: string };
 
export function auditLines(
  e: NativeSyntheticEvent<TextLayoutEventData>
): Violation[] {
  const lines = e.nativeEvent.lines;
  const out: Violation[] = [];
 
  // Line 0 is a line start, but not one produced by wrapping. Skip it.
  for (let i = 1; i < lines.length; i++) {
    const text = lines[i].text ?? "";
    const head = Array.from(text)[0];
    if (!head) continue;
    if (LINE_START_FORBIDDEN.includes(head)) {
      out.push({ lineIndex: i, head, lineText: text });
    }
  }
  return out;
}
 
export function QuoteCard({ quote, onAudit }: { quote: string; onAudit?: (v: Violation[]) => void }) {
  return (
    <Text
      style={{ fontSize: 22, lineHeight: 36 }}
      onTextLayout={(e) => onAudit?.(auditLines(e))}
    >
      {quote}
    </Text>
  );
}

Skipping line 0 keeps the number meaningful: I only want line starts that wrapping created. Source text rarely opens with a comma, but excluding it removes the ambiguity.

I ran all 120 strings through four fixed-width containers. The results:

ConditionViolations / wrapped lines (iOS)Violations / wrapped lines (Android)
Plain Text (defaults)31 / 664 (4.7%)96 / 671 (14.3%)
lineBreakStrategyIOS="push-out" only9 / 682 (1.3%)96 / 671 (14.3%)
textBreakStrategy="balanced" only31 / 664 (4.7%)88 / 669 (13.2%)
Pre-binding (below)0 / 689 (0%)0 / 690 (0%)

Android's 14.3% is the number that landed. Roughly one wrapped line in seven broke in a way a Japanese reader would notice.

push-out helps a great deal on iOS, but the nine survivors were all prolonged sound marks and small kana. Leave the decision to the platform and it will not go that far for you.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Measured violation rates across 120 quotes and 4 device widths, plus the harness that produced them
A WORD JOINER pre-binding implementation that keeps copy, search, and VoiceOver intact
A CI audit that catches double-application, and where Rork Max (SwiftUI) behaves differently
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
Dev Tools2026-07-30
What Renovate may bump in an Expo app, and what it must never touch
Turning on automated dependency updates in a Rork-generated app also hands Renovate the 123 packages Expo SDK 57 pins. Measured on 2026-07-30, six of them sit a full major version behind npm latest. Here is how to generate the ignore list from the SDK instead of maintaining it by hand.
Dev Tools2026-07-28
Counting what prebuild --clean will erase before you upgrade to Expo SDK 57
A raw diff between two generated ios/ trees showed 649 changed lines; only 3 were real edits. How to count what prebuild --clean erases, and move it into a config plugin.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →