From b57b537e84fe04a7c7e74ffd895a7376105ba805 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Tue, 22 Sep 2026 09:04:50 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E8=8B=B1=E8=AA=9ETTS=E3=81=A7Mine=E3=82=92?= =?UTF-8?q?=E3=80=8C=E3=83=9E=E3=82=A4=E3=83=B3=E3=80=8D=E3=81=A8=E8=AA=AD?= =?UTF-8?q?=E3=82=80=E8=AA=A4=E8=AA=AD=E3=82=92=E7=AB=AF=E6=9C=AB=E5=86=85?= =?UTF-8?q?=E8=94=B5TTS=E5=90=91=E3=81=91=E3=81=AE=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E6=9B=BF=E3=81=88=E8=A1=A8=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E3=81=A6=E4=BF=AE=E6=AD=A3=20(#7015)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 5 --- src/utils/englishReading.test.ts | 33 ++++++++++++++++++++++++++++++++ src/utils/englishReading.ts | 33 ++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/utils/englishReading.test.ts b/src/utils/englishReading.test.ts index 883626e73f..4b2c195029 100644 --- a/src/utils/englishReading.test.ts +++ b/src/utils/englishReading.test.ts @@ -34,10 +34,43 @@ describe('fixEnglishReading', () => { ); }); + it('「Mine」を英語 TTS が「みね」と読む表記へ置換する', () => { + expect(fixEnglishReading('Change here for the Mine Line.')).toBe( + 'Change here for the Me-nay Line.' + ); + expect(fixEnglishReading('The next station is MINE.')).toBe( + 'The next station is Me-nay.' + ); + expect(fixEnglishReading('The next station is Nishi-Mine.')).toBe( + 'The next station is Nishi-Me-nay.' + ); + }); + + it('語中の「mine」も前後にハイフンを挟んで置換する', () => { + expect(fixEnglishReading('The next station is Tsurugamine.')).toBe( + 'The next station is Tsuruga-me-nay.' + ); + expect(fixEnglishReading('Mitsumineguchi')).toBe('Mitsu-me-nay-guchi'); + expect(fixEnglishReading('Minenobu')).toBe('Me-nay-nobu'); + expect(fixEnglishReading('Takamine')).toBe('Taka-me-nay'); + // マクロン付きの文字も語の一部として扱う + expect(fixEnglishReading('Minami-Ōmine')).toBe('Minami-Ō-me-nay'); + }); + + it('二重に適用しても結果が変わらない', () => { + expect(fixEnglishReading(fixEnglishReading('Mine Line'))).toBe( + 'Me-nay Line' + ); + expect(fixEnglishReading(fixEnglishReading('Tsurugamine'))).toBe( + 'Tsuruga-me-nay' + ); + }); + it('別語の一部は置換しない', () => { expect(fixEnglishReading('Keiseibus')).toBe('Keiseibus'); // 西武園 (Seibuen) は 1 語なので語単位の一致では対象外 expect(fixEnglishReading('Seibuen')).toBe('Seibuen'); + expect(fixEnglishReading('Minami-Urawa')).toBe('Minami-Urawa'); }); it('対象を含まないテキストはそのまま返す', () => { diff --git a/src/utils/englishReading.ts b/src/utils/englishReading.ts index be492c9327..4abbd5882d 100644 --- a/src/utils/englishReading.ts +++ b/src/utils/englishReading.ts @@ -5,10 +5,28 @@ // 表示用テキストには適用しないこと (TTS 生成時専用)。 type EnglishReadingRule = { - // 置換対象。ASCII 英字の単語境界 (`\b`) で囲み、`Keisei-Ueno` のような - // ハイフン連結の駅名でも語単位で一致させる。 + // 置換対象。原則は ASCII 英字の単語境界 (`\b`) で囲み、`Keisei-Ueno` のような + // ハイフン連結の駅名でも語単位で一致させる。語中も置換する場合 (Mine) は + // reading を関数にして、前後の文字との区切りを補う。 pattern: RegExp; - reading: string; + reading: string | ((match: string, offset: number, text: string) => string); +}; + +// ローマ字表記の文字か。端末内蔵 TTS 向けの経路ではマクロンを除去しないため、 +// 「Ō」などのラテン拡張文字も語の一部として扱う。 +const isRomanLetter = (char: string | undefined): boolean => + char !== undefined && /[A-Za-z\u00C0-\u024F]/.test(char); + +// 「mine」を読み替え先へ置換する。Tsurugamine のように語中にある場合も置換し、 +// 前後の文字とつながって別の綴りとして読まれないよう、接する側にハイフンを挟む。 +// 先頭の大文字・小文字は元の綴りに合わせる (Mine → Me-nay, Takamine → Taka-me-nay)。 +// TrainLCD/functions の normalizeRomanText (replaceMine) と同じ置換にして、 +// リモート TTS が無効な回とも読みを揃える。 +const toMineReading = (match: string, offset: number, text: string) => { + const before = isRomanLetter(text[offset - 1]) ? '-' : ''; + const after = isRomanLetter(text[offset + match.length]) ? '-' : ''; + const head = /[A-Z]/.test(match.charAt(0)) ? 'Me' : 'me'; + return `${before}${head}-nay${after}`; }; // NOTE: 読み替え先は必ず英語の辞書語 (か、辞書語のハイフン連結) にする。 @@ -20,6 +38,10 @@ const ENGLISH_READING_RULES: readonly EnglishReadingRule[] = [ // 「Seibu (西武)」も同じく "ei" を /aɪ/ と推定して「さいぶ」と読むため、 // "Say" + "boo" で /seɪ.buː/ (せいぶ) を確定させる。 { pattern: /\bSeibu\b/gi, reading: 'Say-boo' }, + // 「Mine (美祢・峰など)」は英語 TTS が英単語 "mine" (まいん) として読むため、 + // "Me" + "nay" で /mi.neɪ/ (みね) に寄せる。Tsurugamine・Mitsumineguchi の + // ように語中にある場合も対象にするため、単語境界では区切らない。 + { pattern: /mine/gi, reading: toMineReading }, ]; /** @@ -27,6 +49,9 @@ const ENGLISH_READING_RULES: readonly EnglishReadingRule[] = [ */ export const fixEnglishReading = (text: string): string => ENGLISH_READING_RULES.reduce( - (acc, { pattern, reading }) => acc.replace(pattern, reading), + (acc, { pattern, reading }) => + typeof reading === 'string' + ? acc.replace(pattern, reading) + : acc.replace(pattern, reading), text ); From 79929e8ac84a76a4a524130c9d07544d76d7c73a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:06:38 +0900 Subject: [PATCH 2/2] Bump version for canary release (#7017) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 302abb3031..e098d15f09 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -167,12 +167,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000779 + versionCode 100000781 versionName "10.15.1" } prod { dimension "environment" - versionCode 100000779 + versionCode 100000781 versionName "10.15.1" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -183,7 +183,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000779 + versionCode 100000781 versionName "10.15.1" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index f69c3189b4..97533ab159 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000780 + versionCode = 100000782 versionName = "10.15.1" } create("prod") { dimension = "environment" - versionCode = 100000780 + versionCode = 100000782 versionName = "10.15.1" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000780 + versionCode = 100000782 versionName = "10.15.1" } } diff --git a/app.config.ts b/app.config.ts index 2a7a40fbfd..63b83c8edd 100644 --- a/app.config.ts +++ b/app.config.ts @@ -57,7 +57,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2919', + buildNumber: '2920', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -71,7 +71,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000779, + versionCode: 100000781, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 799f1e6ec3..8415fc739a 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2919; + CURRENT_PROJECT_VERSION = 2920; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO;