From 898c167a0e15bc5888fabcdf70993012a00b17e2 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Thu, 5 Mar 2026 22:41:18 +0000 Subject: [PATCH] fix: replace line-name suffixes with English IPA instead of stripping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of just removing 線/本線/支線 from the IPA, replace them with their English IPA equivalents (laɪn / meɪn laɪn) so that Google TTS correctly pronounces the line type in English. Co-Authored-By: Claude Opus 4.6 --- stationapi/src/domain/ipa.rs | 63 ++++++++++++++++------------- stationapi/src/use_case/dto/line.rs | 9 +++-- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/stationapi/src/domain/ipa.rs b/stationapi/src/domain/ipa.rs index 1c204954..143370a8 100644 --- a/stationapi/src/domain/ipa.rs +++ b/stationapi/src/domain/ipa.rs @@ -1,25 +1,32 @@ -/// Common katakana suffixes for line names, ordered longest-first for greedy matching. -const LINE_NAME_SUFFIXES: &[&str] = &["ホンセン", "シセン", "セン"]; -/// Suffixes that should NOT be stripped even though they end with セン. +/// Katakana line-name suffixes paired with their English IPA replacements. +/// Ordered longest-first for greedy matching. +const LINE_NAME_SUFFIX_MAP: &[(&str, &str)] = &[ + ("ホンセン", " meɪn laɪn"), + ("シセン", " laɪn"), + ("セン", " laɪn"), +]; +/// Suffixes that should NOT be replaced even though they end with セン. const LINE_NAME_SUFFIX_EXCEPTIONS: &[&str] = &["シンカンセン"]; -/// Strip a common line-name suffix (線/本線/支線) from a katakana string. +/// Replace a common line-name suffix (線/本線/支線) in a katakana string +/// with its English IPA equivalent (Line / Main Line). /// 新幹線 (Shinkansen) is preserved as it is used as-is in English. -/// Returns the stem (without the suffix). If no known suffix is found, returns the input unchanged. -pub fn strip_line_name_suffix(input: &str) -> &str { +/// Returns the stem and the English IPA suffix to append. +/// If no known suffix is found, returns the full input with an empty suffix. +pub fn replace_line_name_suffix(input: &str) -> (&str, &str) { for exception in LINE_NAME_SUFFIX_EXCEPTIONS { if input.ends_with(exception) { - return input; + return (input, ""); } } - for suffix in LINE_NAME_SUFFIXES { + for (suffix, replacement) in LINE_NAME_SUFFIX_MAP { if let Some(stem) = input.strip_suffix(suffix) { if !stem.is_empty() { - return stem; + return (stem, replacement); } } } - input + (input, "") } /// Convert a katakana string to its IPA transcription. @@ -634,54 +641,54 @@ mod tests { } // ============================================ - // strip_line_name_suffix tests + // replace_line_name_suffix tests // ============================================ #[test] - fn test_strip_sen() { + fn test_replace_sen() { assert_eq!( - strip_line_name_suffix("セイブイケブクロセン"), - "セイブイケブクロ" + replace_line_name_suffix("セイブイケブクロセン"), + ("セイブイケブクロ", " laɪn") ); } #[test] - fn test_strip_honsen() { + fn test_replace_honsen() { assert_eq!( - strip_line_name_suffix("トウカイドウホンセン"), - "トウカイドウ" + replace_line_name_suffix("トウカイドウホンセン"), + ("トウカイドウ", " meɪn laɪn") ); } #[test] - fn test_strip_shinkansen_preserved() { + fn test_replace_shinkansen_preserved() { // 新幹線(Shinkansen)は英語でもそのまま使われるので除去しない assert_eq!( - strip_line_name_suffix("トウホクシンカンセン"), - "トウホクシンカンセン" + replace_line_name_suffix("トウホクシンカンセン"), + ("トウホクシンカンセン", "") ); } #[test] - fn test_strip_shisen() { + fn test_replace_shisen() { assert_eq!( - strip_line_name_suffix("ナガノハラクサツグチシセン"), - "ナガノハラクサツグチ" + replace_line_name_suffix("ナガノハラクサツグチシセン"), + ("ナガノハラクサツグチ", " laɪn") ); } #[test] - fn test_strip_no_suffix() { + fn test_replace_no_suffix() { // ライン等セン以外の末尾はそのまま返す assert_eq!( - strip_line_name_suffix("ショウナンシンジュクライン"), - "ショウナンシンジュクライン" + replace_line_name_suffix("ショウナンシンジュクライン"), + ("ショウナンシンジュクライン", "") ); } #[test] - fn test_strip_bare_sen_returns_unchanged() { + fn test_replace_bare_sen_returns_unchanged() { // "セン" だけの場合、stemが空になるので除去しない - assert_eq!(strip_line_name_suffix("セン"), "セン"); + assert_eq!(replace_line_name_suffix("セン"), ("セン", "")); } } diff --git a/stationapi/src/use_case/dto/line.rs b/stationapi/src/use_case/dto/line.rs index 734e65c7..79a0f438 100644 --- a/stationapi/src/use_case/dto/line.rs +++ b/stationapi/src/use_case/dto/line.rs @@ -1,15 +1,18 @@ use crate::{ domain::{ entity::{gtfs::TransportType, line::Line}, - ipa::{katakana_to_ipa, strip_line_name_suffix}, + ipa::{katakana_to_ipa, replace_line_name_suffix}, }, proto::{Line as GrpcLine, TransportType as GrpcTransportType}, }; impl From for GrpcLine { fn from(line: Line) -> Self { - let name_ipa = katakana_to_ipa(strip_line_name_suffix(&line.line_name_k)) - .filter(|ipa| !ipa.is_empty()); + let name_ipa = { + let (stem, suffix_ipa) = replace_line_name_suffix(&line.line_name_k); + katakana_to_ipa(stem).map(|ipa| format!("{ipa}{suffix_ipa}")) + } + .filter(|ipa| !ipa.is_empty()); // バス路線の場合は line_type を OtherLineType (0) に強制 // (鉄道用の line_type が誤って設定されている可能性があるため) let line_type = if line.transport_type == TransportType::Bus {