Skip to content

StationとLineにnameIpaフィールドを追加しTTS IPA置換をアプリ側に移行#5524

Merged
TinyKitten merged 4 commits intodevfrom
feature/add-name-ipa-field
Mar 5, 2026
Merged

StationとLineにnameIpaフィールドを追加しTTS IPA置換をアプリ側に移行#5524
TinyKitten merged 4 commits intodevfrom
feature/add-name-ipa-field

Conversation

@TinyKitten
Copy link
Member

@TinyKitten TinyKitten commented Mar 5, 2026

Summary

  • BFFのステージング環境で追加されたnameIpaフィールドをStation・Line型のGraphQLクエリ・型定義に追加
  • アプリ側のTTSテキスト構築時にnameIpa<phoneme>タグでラップするwrapIpaヘルパーを導入
  • Cloud Function (tts.ts) のハードコードされたIPA phoneme正規表現置換(約130行)を削除し、アプリから送信されるSSMLに委譲

Test plan

  • npm run typecheck パス
  • npm run lint パス
  • npm test (136 suites, 1259 tests) パス
  • functions/npm run build パス
  • ステージング環境でTTS音声再生の動作確認

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • 新機能

    • 駅名・路線名をIPAで包む(phoneme)ラップ処理を導入し、発音の指定表示を可能にしました。
    • SSML生成でXMLエスケープを行う補助処理を追加しました。
  • 改善

    • 読み上げテキスト生成にIPAラップを一貫適用し、英語/日本語の発音精度を向上しました。
    • SSML生成時に既存の発音タグが無い場合のみレガシーIPA置換のフォールバックを適用するようにしました。
  • テスト / フィクスチャ

    • テストデータ・フィクスチャ類にIPAフィールドを追加し、発音データ形状を整備しました。

TinyKitten and others added 2 commits March 6, 2026 01:16
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9125868-5f59-4404-a148-91a22585e4d8

📥 Commits

Reviewing files that changed from the base of the PR and between 334c5c0 and c6f1933.

📒 Files selected for processing (2)
  • functions/src/funcs/tts.ts
  • src/utils/ssml.ts

📝 Walkthrough

Walkthrough

GraphQL フラグメントに nameIpa を追加し、フロントエンドの TTS テキスト生成で駅名・路線名を IPA 用 <phoneme> にラップする wrapIpa を導入。サーバー側 TTS は英語 SSML に <phoneme> が無い場合に applyLegacyIpaReplacements を条件適用するよう変更。

Changes

Cohort / File(s) Summary
TTS サーバー処理
functions/src/funcs/tts.ts, functions/src/utils/legacyIpa.ts
applyLegacyIpaReplacements(text) を追加し、英語 SSML に <phoneme> が含まれない場合のみレガシー IPA 置換を適用する分岐を追加。全角記号の正規表現を修正/調整。
IPA ラップユーティリティ
src/utils/ssml.ts
wrapIpa(nameRoman, nameIpa) を追加(nameIpa があれば <phoneme alphabet="ipa" ph="..."> でラップ)。XML エスケープ補助を追加。
TTS テキスト生成フック
src/hooks/useTTSText.ts, src/hooks/useBusTTSText.ts
駅名・路線名の各所で wrapIpa を適用するよう変更。useMemo による変換を追加し、マッピングや型利用を調整。
GraphQL フラグメント拡張
src/lib/graphql/queries.ts
複数フラグメント(LINE_NESTED_FRAGMENT, STATION_FRAGMENT, STATION_PRESET_FRAGMENT, STATION_LIGHT_FRAGMENT, LINE_ROUTE_FRAGMENT 等)に nameIpa を追加。
フィクスチャ/テスト更新
src/__fixtures__/line.ts, src/utils/__fixtures__/search.ts, src/utils/jr.ts, src/utils/test/factories.ts, src/hooks/*.{test.tsx,tsx}
テスト/フィクスチャ/テストファクトリに nameIpa を追加(undefined / null)。テストデータ形状の拡張のみ。

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Hook as useTTSText / useBusTTSText
  participant Data as GraphQL / Fixtures
  participant Wrap as wrapIpa (src/utils/ssml.ts)
  participant TTSFunc as functions/src/funcs/tts.ts
  participant Legacy as applyLegacyIpaReplacements

  Client->>Hook: 表示/読み上げデータ要求
  Hook->>Data: `nameRoman` / `nameIpa` を取得
  Hook->>Wrap: wrapIpa(nameRoman, nameIpa) を適用
  Wrap-->>Hook: SSML 断片(場合により `<phoneme>` 含む)
  Hook->>TTSFunc: 完成した SSML を送信
  TTSFunc->>TTSFunc: 英語 SSML に `<phoneme>` があるか判定
  alt `<phoneme>` が無い
    TTSFunc->>Legacy: applyLegacyIpaReplacements(text)
    Legacy-->>TTSFunc: 置換済 SSML
  end
  TTSFunc-->>Client: 合成用 SSML を返す
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

react, functions

Poem

🐰 ぴょんと跳んで名を抱きしめる
ローマ字に声を添え、発音をつつむ
フラグメントは名を渡し、ラップは鳴る
レガシーはひっそり、必要な時だけ出るよ
新しい声で駅が春を告げる ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、Station と Line に nameIpa フィールドを追加し、TTS IPA 置換をアプリ側に移行することを明確に説明しており、変更内容の主要なポイントを正確に要約している。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/add-name-ipa-field

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/utils/ssml.ts (1)

6-17: nameIpa に含まれる可能性のある特殊文字のエスケープを検討してください。

nameIpa の値が ph 属性内に直接埋め込まれていますが、ダブルクォート (") や <>& などのXML特殊文字が含まれる場合、SSMLの構文が壊れる可能性があります。

BFFから返されるデータが信頼できる場合は問題ありませんが、防御的にエスケープを追加することを検討してください。

♻️ エスケープ処理の追加案
+const escapeXmlAttr = (str: string): string =>
+  str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
 export const wrapIpa = (
   nameRoman: string | null | undefined,
   nameIpa: string | null | undefined
 ): string => {
   if (!nameRoman) {
     return '';
   }
   if (!nameIpa) {
     return nameRoman;
   }
-  return `<phoneme alphabet="ipa" ph="${nameIpa}">${nameRoman}</phoneme>`;
+  return `<phoneme alphabet="ipa" ph="${escapeXmlAttr(nameIpa)}">${nameRoman}</phoneme>`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ssml.ts` around lines 6 - 17, The wrapIpa function currently
injects nameIpa directly into the ph attribute which can break SSML if nameIpa
contains XML-special characters; update wrapIpa to escape XML characters (&, <,
>, ", and ') in nameIpa before embedding it into the ph attribute (and also
escape nameRoman used as element content), preserving the existing
null/undefined handling and return values; reference the wrapIpa function and
the nameIpa/nameRoman parameters and replace those characters with their XML
entities (&amp;, &lt;, &gt;, &quot;, &apos;) so the returned string is always
valid SSML.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@functions/src/funcs/tts.ts`:
- Line 41: The regex used in the replace call
(.replace(/[!-/:-@[-`{-~、-〜”'・]+/g, ' ')) is currently stripping a half-width
apostrophe (') which can break words like O'Hare or SSML with single quotes;
update that character class to remove the ASCII apostrophe so it no longer
matches (e.g., .replace(/[!-/:-@[-`{-~、-〜”・]+/g, ' ')), and if you intended to
target the fullwidth apostrophe include the explicit fullwidth character U+FF07
(') instead; modify the replace expression in funcs/tts.ts (the .replace(...)
call) accordingly.

In `@src/hooks/useTTSText.ts`:
- Around line 56-71: The wrapIpa usage in connectedLines and transferLines can
inject raw XML-special characters into SSML via nameRoman/nameIpa; update
wrapIpa (in src/utils/ssml.ts) to XML-escape input strings (at least &, <, >, ",
') before composing/returning the SSML fragment so returned values are safe to
embed, ensure the escaping is idempotent (don’t double-escape already-escaped
input) and keep existing wrapIpa signature so connectedLines and transferLines
code (which calls wrapIpa) requires no other changes.

---

Nitpick comments:
In `@src/utils/ssml.ts`:
- Around line 6-17: The wrapIpa function currently injects nameIpa directly into
the ph attribute which can break SSML if nameIpa contains XML-special
characters; update wrapIpa to escape XML characters (&, <, >, ", and ') in
nameIpa before embedding it into the ph attribute (and also escape nameRoman
used as element content), preserving the existing null/undefined handling and
return values; reference the wrapIpa function and the nameIpa/nameRoman
parameters and replace those characters with their XML entities (&amp;, &lt;,
&gt;, &quot;, &apos;) so the returned string is always valid SSML.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0c11e12-5a47-4bef-baf5-ecb5215ad18c

📥 Commits

Reviewing files that changed from the base of the PR and between 439852f and ba4d184.

📒 Files selected for processing (13)
  • functions/src/funcs/tts.ts
  • src/@types/graphql.d.ts
  • src/__fixtures__/line.ts
  • src/hooks/useBusTTSText.ts
  • src/hooks/useGetStationsWithTermination.test.tsx
  • src/hooks/useRefreshStation.test.tsx
  • src/hooks/useTTSText.ts
  • src/hooks/useTransferLinesFromStation.test.tsx
  • src/lib/graphql/queries.ts
  • src/utils/__fixtures__/search.ts
  • src/utils/jr.ts
  • src/utils/ssml.ts
  • src/utils/test/factories.ts

TinyKitten and others added 2 commits March 6, 2026 01:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
functions/src/funcs/tts.ts (1)

42-42: ⚠️ Potential issue | 🟠 Major

Line 42: 半角アポストロフィの除去はやめてください。

' を削ると、<phoneme ... ph="..."> の値や O'Hare のような表記を壊します。過去指摘と同じ懸念が未解消です。

修正案
-    .replace(/[!-/:-@[-`{-~、-〜”'・]+/g, ' ')
+    .replace(/[!-/:-@[-`{-~、-〜”・]+/g, ' ')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/src/funcs/tts.ts` at line 42, The regex in the string-cleaning call
.replace(/[!-/:-@[-`{-~、-〜”'・]+/g, ' ') is currently removing the ASCII
apostrophe (') and breaks phoneme attributes and names like O'Hare; update that
replace so it does not strip the half-width apostrophe by removing the '
character from the character class (leave other full-width punctuation and
quotes intact) and keep the same global replacement behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@functions/src/funcs/tts.ts`:
- Line 42: The regex in the string-cleaning call
.replace(/[!-/:-@[-`{-~、-〜”'・]+/g, ' ') is currently removing the ASCII
apostrophe (') and breaks phoneme attributes and names like O'Hare; update that
replace so it does not strip the half-width apostrophe by removing the '
character from the character class (leave other full-width punctuation and
quotes intact) and keep the same global replacement behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92a6d6a9-b0eb-4c4e-808e-dd4aa89a460b

📥 Commits

Reviewing files that changed from the base of the PR and between ba4d184 and 334c5c0.

📒 Files selected for processing (2)
  • functions/src/funcs/tts.ts
  • functions/src/utils/legacyIpa.ts

@TinyKitten
Copy link
Member Author

@coderabbitai 前回のコミットで既に半角アポストロフィ ' は除去済みです。対応不要です。

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Tip

For best results, initiate chat on the files or code changes.

@TinyKitten ご確認ありがとうございます。前回のコミットで対応済みとのこと、承知しました。


🧠 Learnings used
Learnt from: CR
Repo: TrainLCD/MobileApp PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-27T08:01:30.350Z
Learning: Write commit messages in Japanese (e.g., `テレメトリー送信機をリファクタリングしてnull状態を回避`)

Learnt from: CR
Repo: TrainLCD/MobileApp PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T08:01:44.056Z
Learning: Commit messages must be single-sentence statements in Japanese (e.g., `テレメトリー送信機をリファクタリングしてnull状態を回避`); prefix production hot fixes with `Hotfix:`

Learnt from: CR
Repo: TrainLCD/MobileApp PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-27T08:01:30.350Z
Learning: Generate commit messages in Japanese

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 4511
File: package.json:43-66
Timestamp: 2025-09-22T09:21:21.741Z
Learning: TinyKittenはExpo SDK 54アップグレードにおいて、babel.config.js、RCTNewArchEnabled設定、Podfile設定を適切に処理している。App ClipのiOS deployment targetの調整のみが必要な状況。

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 0
File: :0-0
Timestamp: 2025-08-11T21:13:39.083Z
Learning: TinyKitten prefers concise code reviews for the TrainLCD/MobileApp repository. Keep review comments brief and to the point.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5464
File: android/app/src/main/java/me/tinykitten/trainlcd/MainApplication.kt:33-37
Timestamp: 2026-02-27T07:30:06.268Z
Learning: TinyKitten prefers not to modify code taken from official templates (e.g., Expo, React Native) to maintain alignment with upstream and ease future updates.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 4448
File: functions/src/funcs/tts.ts:214-232
Timestamp: 2025-08-11T20:23:23.071Z
Learning: PR `#4448` in TrainLCD/MobileApp is specifically about AI feedback summarization and labeling functionality, not about TTS (Text-to-Speech) improvements. The TTS code changes appear to be part of a modularization refactoring but are not the focus of this PR.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 4890
File: src/components/LineBoard.tsx:41-50
Timestamp: 2026-01-02T09:13:15.829Z
Learning: In LineBoard.tsx for bus lines (TransportType.Bus), only the `nameRoman` field requires parentheses removal via `parenthesisRegexp.replace()`. Other language fields (name, nameKatakana, nameChinese, nameKorean) should remain unchanged in the slicedLeftStations mapping.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 4286
File: src/components/HeaderLED.tsx:5-10
Timestamp: 2025-05-19T10:59:37.813Z
Learning: The hooks useCurrentStation, useNextStation, useIsNextLastStop, and useNumbering are properly exported in src/hooks/index.ts, allowing them to be imported from '../hooks' in src/components/HeaderLED.tsx as implemented in PR `#4286`.

@TinyKitten TinyKitten merged commit c28ea4c into dev Mar 5, 2026
1 check passed
@TinyKitten TinyKitten deleted the feature/add-name-ipa-field branch March 5, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant