Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
「魔法少女まどか☆マギカ Magia Exedra」のグローバル展開を支える、開発チームと翻訳...
Search
gree_tech
PRO
September 01, 2025
Technology
1.7k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
「魔法少女まどか☆マギカ Magia Exedra」のグローバル展開を支える、開発チームと翻訳チームの「意識しない協創」を実現するローカライズシステム
CEDEC2025で発表された資料です。
https://cedec.cesa.or.jp/2025/timetable/detail/s67af0e71ed4e6/
gree_tech
PRO
September 01, 2025
More Decks by gree_tech
See All by gree_tech
変わるもの、変わらないもの :OSSアーキテクチャで実現する持続可能なシステム
gree_tech
PRO
0
4.8k
マネジメントに役立つ Google Cloud
gree_tech
PRO
0
65
今この時代に技術とどう向き合うべきか
gree_tech
PRO
3
2.8k
生成AIを開発組織にインストールするために: REALITYにおけるガバナンス・技術・文化へのアプローチ
gree_tech
PRO
0
450
安く・手軽に・現場発 既存資産を生かすSlack×AI検索Botの作り方
gree_tech
PRO
0
440
生成AIを安心して活用するために──「情報セキュリティガイドライン」策定とポイント
gree_tech
PRO
1
2.4k
あうもんと学ぶGenAIOps
gree_tech
PRO
0
560
MVP開発における生成AIの活用と導入事例
gree_tech
PRO
0
590
機械学習・生成AIが拓く事業価値創出の最前線
gree_tech
PRO
0
470
Other Decks in Technology
See All in Technology
Kotlin 開発のツラミを爆破した話! / Explode the difficulty of Kotlin dev!
eller86
0
140
打造你的 AI 工作流:Agent Skill + MCP 實戰工作坊
appleboy
0
450
スタートアップにおけるアジャイルの実践について #shibuyagile
murabayashi
3
2k
AI・ロボティクスと自動化社会 / AI, Robotics, and the Automated Society
ks91
PRO
0
150
テスト設計の本質を改めて考えてみる~生成AIを活用する時代だからこそ、作ったテストの説明性を高めよう~
yamasaki696
1
280
次世代ランサムウェア対策の考察 / 20260704 Mitsutoshi Matsuo
shift_evolve
PRO
5
1.7k
GitHub Copilot運用のリアル ~AI Credit時代にどう向き合うか~
takafumisu2uk1
0
620
AI駆動開発におけるQAエンジニアの役割事例 〜AI駆動開発の現場から〜
kobayashiyorimitsu
0
280
勉強会企画をアプリで構造化してみた 〜そこで見えた、AIとの付き合い方〜 / I've structured a study group plan using an app.
pauli
0
290
事業価値を⽣み出すSREへ SREが担うべき意思決定の5層
kenta_hi
0
110
組織における AI-DLC 実践
askul
0
300
「ビジネスがわかるエンジニア」とは何か?
ryooob
0
490
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
55
10k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Prompt Engineering for Job Search
mfonobong
0
360
How to Ace a Technical Interview
jacobian
281
24k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Google's AI Overviews - The New Search
badams
0
1.1k
How to Think Like a Performance Engineer
csswizardry
28
2.7k
The Cult of Friendly URLs
andyhume
79
6.9k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
380
Transcript
©WFS
「魔法少女まどか☆マギカ Magia Exedra」の グローバル展開を支える、開発チームと翻訳チームの 「意識しない協創」を実現するローカライズシステム Masashi HARADA / Isao SHINOHARA
2025.07.23 ©WFS
◈ 『魔法少女まどかマギカ』の世界を 3Dグラフィックで追体験! ◈ 歴代魔法少女が織りなすシンプルで奥 深いバトルシステム! ◈ 日本語・英語、20の国と地域で展開 |魔法少女まどか☆マギカ Magia
Exedra ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 2 -
|セッションについて ◈ アプリを複数言語、同時リリースを実現する ためのローカライズシステムの紹介 ◈ 開発、翻訳チームの自律を支える自動化やツ ールなども紹介 ◈ 翻訳していて困ったことも実例で紹介 ◈
講演資料は講演後にCEDiLにて公開します ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 3 -
|原田 大志 Masashi HARADA ◈ 2020年に株式会社ポケラボに入社、現在は 株式会社WFSに所属。 ◈ 新規プロダクトの開発をしながら、開発効 率化のためのツール制作など
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 自己紹介 - 4 -
|篠原 功 Isao SHINOHARA ◈ 2013年に株式会社ポケラボに入社、 現在は株式会社WFSに所属 ローカライズシステムのほか リソース管理システムなどを担当 ◈
開発・運用: 「SINoALICE -シノアリス-」 「アサルトリリィ Last Bullet」 基盤: 「魔法少女まどか☆マギカ Magia Exedra」 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 自己紹介 - 5 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 6 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 7 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 8 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 9 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 10 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 11 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 12 -
課題と目標 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 13 -
|内容 ❖運営型ゲームの多言語対応の難しさ ❖目指したローカライズシステムの姿 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 課題と目標
- 14 -
|内容 ❖運営型ゲームの多言語対応の難しさ ❖目指したローカライズシステムの姿 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 課題と目標
- 15 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS アプリでやっていること 運営型ゲームの多言語対応の難しさ -
16 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 問題点 ◈ 機能開発が忙しすぎる
◈ 散在する膨大な翻訳対象を管理しきれない ◈ スケジュールのコントロールが難しい ◈ 継続して追加、変更が発生する 運営型ゲームの多言語対応の難しさ - 17 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 問題点 ◈ 機能開発が忙しすぎる
◈ 散在する膨大な翻訳対象を管理しきれない ◈ スケジュールのコントロールが難しい ◈ 継続して追加、変更が発生する 運営型ゲームの多言語対応の難しさ - 18 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 機能開発が忙しすぎる ✓ 多言語対応がなくても余裕なんてない
✓ そこにタスクを上乗せできるわけない ✓ そもそも日本語版の開発がマスト ✓ 開発と並行で作業が発生し続ける 運営型ゲームの多言語対応の難しさ - 19 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 問題点 ◈ 機能開発が忙しすぎる
◈ 散在する膨大な翻訳対象を管理しきれない ◈ スケジュールのコントロールが難しい ◈ 継続して追加、変更が発生する 運営型ゲームの多言語対応の難しさ - 20 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 散在する膨大な翻訳対象を管理しきれない ✓ UIもデータもシナリオも対象は大量
✓ 1日分の変更量だって把握しきれない ✓ 変更する人も大勢いる ✓ 追加、変更を一元管理するのは難しい 運営型ゲームの多言語対応の難しさ - 21 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 問題点 ◈ 機能開発が忙しすぎる
◈ 散在する膨大な翻訳対象を管理しきれない ◈ スケジュールのコントロールが難しい ◈ 継続して追加、変更が発生する 運営型ゲームの多言語対応の難しさ - 22 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS スケジュールのコントロールが難しい ✓ いつ、どれくらいの量がくるのか予測できない
✓ リリース日程にも影響がでてしまう ✓ リリースを分けても問題がある 運営型ゲームの多言語対応の難しさ - 23 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 問題点 ◈ 機能開発が忙しすぎる
◈ 散在する膨大な翻訳対象を管理しきれない ◈ スケジュールのコントロールが難しい ◈ 継続して追加、変更が発生する 運営型ゲームの多言語対応の難しさ - 24 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 継続して追加、変更が発生する ✓ 終わったと思っても変更は発生する
✓ 変更が入れば翻訳作業も発生する ✓ チーム間で擦り合わせるのが難しい 運営型ゲームの多言語対応の難しさ - 25 -
|内容 ❖運営型ゲームの多言語対応の難しさ ❖目指したローカライズシステムの姿 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 課題と目標
- 26 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 開発チームが開発に注力できること 開発チーム Developer
Repository 開発チームの領域に関してはなるべく 変更はしない。 今まで通りの開発をしていればOK。 目指したローカライズシステムの姿 - 27 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 翻訳チームのタイミングで翻訳対象を取得できること 開発チーム Developer
Extractor Repository Text + Meta リポジトリからその時点での最新 のデータを全て抽出する。 開発チームを介さずに自由に実行 できる。 目指したローカライズシステムの姿 - 28 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 追加、変更含めて全体像が把握できること 開発チーム 翻訳チーム
Developer Translator Extractor Repository Management Tool Text + Meta 抽出データを翻訳チームの管理システ ムに取り込む。 日本語をキーにして管理することで重 複は省かれ、未登録の日本語だけが新 規として取り込まれる。 翻訳テキスト以外のメタ情報と合わせ て翻訳を行う。 目指したローカライズシステムの姿 - 29 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 開発、翻訳チームの作業がお互いに影響を与えないこと 開発チーム 翻訳チーム
Developer Translator Extractor Repository Management Tool Reflector Text + Meta 翻訳データは開発チームの作業とコンフリクト しないように、専用のファイルとしてコミット される。 プロダクトは翻訳データがあれば使うが、なけ ればそのまま動作するようにしておく。 目指したローカライズシステムの姿 - 30 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 抽出機と反映機 開発チーム 翻訳チーム
Developer Translator Repository Management Tool Text + Meta Extractor Reflector 目指したローカライズシステムの姿 - 31 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 32 -
設計 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 33 -
|内容 ❖どうやって元の位置に反映するか? ❖メタ情報の取り扱い ❖翻訳テキストの納品 ❖動的反映と静的反映 ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS 設計 - 34 -
|内容 ❖どうやって元の位置に反映するか? ❖メタ情報の取り扱い ❖翻訳テキストの納品 ❖動的反映と静的反映 ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS 設計 - 35 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS どうやって元の位置に反映するか? どこに戻せば いいんだろう?
Fate Weave 設計 - 36 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS どうやって元の位置に反映するか? 場所は違ってもテキストの変化は 一緒。
「編成」をキーにして 「Assign」が取得できればいい。 翻訳テーブル Japanese English ユニオン Union 編成 Assign パーティ Party 設計 - 37 -
|内容 ❖どうやって元の位置に反映するか? ❖メタ情報の取り扱い ❖翻訳テキストの納品 ❖動的反映と静的反映 ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS 設計 - 38 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS メタ情報の取り扱い 設計 -
39 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS メタ情報の取り扱い 設計 -
40 -
|内容 ❖どうやって元の位置に反映するか? ❖メタ情報の取り扱い ❖翻訳テキストの納品 ❖動的反映と静的反映 設計 - 41 - ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 翻訳テキストの納品 設計 -
42 -
|内容 ❖どうやって元の位置に反映するか? ❖メタ情報の取り扱い ❖翻訳テキストの納品 ❖動的反映と静的反映 設計 - 43 - ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 動的反映と静的反映 Japanese English
ユニオン Union パーティ Party クエスト Quests 翻訳テーブル 画面の初期化時に、各テキストコンポーネント自身が、 翻訳テーブルから取得する。 埋め込みなどの準備は必要ない。 設計 - 44 -
| ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 日本語字幕を元に、翻訳テーブルを使 って英語用の字幕ファイルを生成。 動的反映と静的反映
英語 + + 日本語 動画は共通 設計 - 45 -
©WFS ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 46 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 47 -
実装 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 48 -
|実装方針 ◈ 開発者に負担が少ないこと ◈ 抽出、表示・反映に抜け漏れが発生しないこと 上記が満たせない実装方法は禁止!! 実装 - 49 -
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
|使用技術 ◈ クライアント: Unity, C# ◈ サーバー: PHP, YAML ◈
DB: Spanner ◈ ストリーミング動画: HLS 実装 - 50 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 51 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 52 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 53
-
◈ 翻訳用のコンポーネントを用意 ◈ TextMeshProがアタッチされて いるGameObjectにアタッチ |実装ルール ©2024 Magica Quartet/Aniplex,Magia Exedra
Project ©WFS Unity UIテキストコンポーネント - 54 -
◈ AssetDatabase.FindAssets("t:Scene t:Prefab"); でシーンやプレハブのアセットを取得 ◈ テキストコンポーネントから翻訳対象となる 日本語テキストを抽出 |抽出方法 ©2024 Magica
Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 55 -
©WFS var guids = AssetDatabase.FindAssets("t:Scene t:Prefab"); foreach (var guid in
guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var asset = AssetDatabase.LoadMainAssetAtPath(path); if (asset is SceneAsset) { var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive); foreach (var root in scene.GetRootGameObjects()) { foreach (var component in root.GetComponentsInChildren<TextMeshPro>(true)) { // Unityシーン: TextMeshProに入力された日本語テキストをリストに追加 texts.Add(component.text); } } EditorSceneManager.CloseScene(scene, true); } else if (asset is GameObject prefab) { foreach (var component in prefab.GetComponentsInChildren<TextMeshPro>(true)) { // Unityプレハブ: TextMeshProに入力された日本語テキストリストに追加 texts.Add(component.text); } } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 56 -
©WFS var guids = AssetDatabase.FindAssets("t:Scene t:Prefab"); foreach (var guid in
guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var asset = AssetDatabase.LoadMainAssetAtPath(path); if (asset is SceneAsset) { var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive); foreach (var root in scene.GetRootGameObjects()) { foreach (var component in root.GetComponentsInChildren<TextMeshPro>(true)) { // Unityシーン: TextMeshProに入力された日本語テキストをリストに追加 texts.Add(component.text); } } EditorSceneManager.CloseScene(scene, true); } else if (asset is GameObject prefab) { foreach (var component in prefab.GetComponentsInChildren<TextMeshPro>(true)) { // Unityプレハブ: TextMeshProに入力された日本語テキストリストに追加 texts.Add(component.text); } } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 57 -
©WFS var scene = EditorSceneManager.OpenScene( path, OpenSceneMode.Additive); foreach (var root
in scene.GetRootGameObjects()) { 〜〜〜〜〜〜〜〜〜〜〜 省略 〜〜〜〜〜〜〜〜〜〜〜〜〜〜 } EditorSceneManager.CloseScene(scene, true); ◈ 取得したいシーンをオープン ◈ ルートオブジェクトからGameObjectを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 58 -
©WFS var scene = EditorSceneManager.OpenScene( path, OpenSceneMode.Additive); foreach (var root
in scene.GetRootGameObjects()) { 〜〜〜〜〜〜〜〜〜〜〜 省略 〜〜〜〜〜〜〜〜〜〜〜〜〜〜 } EditorSceneManager.CloseScene(scene, true); ◈ 取得したいシーンをオープン ◈ ルートオブジェクトからGameObjectを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 59 -
©WFS var scene = EditorSceneManager.OpenScene( path, OpenSceneMode.Additive); foreach (var root
in scene.GetRootGameObjects()) { 〜〜〜〜〜〜〜〜〜〜〜 省略 〜〜〜〜〜〜〜〜〜〜〜〜〜〜 } EditorSceneManager.CloseScene(scene, true); ◈ 取得したいシーンをオープン ◈ ルートオブジェクトからGameObjectを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 60 -
©WFS var components = root.GetComponentsInChildren<TextMeshPro>(true); foreach (var component in components)
{ // TextMeshProで入力された日本語テキストをリストに追加 texts.Add(component.text); } ◈ GameObjectからTextMeshProを取得 ◈ 入力された日本語テキストを抽出 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 61 -
©WFS var components = root.GetComponentsInChildren<TextMeshPro>(true); foreach (var component in components)
{ // TextMeshProで入力された日本語テキストをリストに追加 texts.Add(component.text); } ◈ GameObjectからTextMeshProを取得 ◈ 入力された日本語テキストを抽出 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 62 -
◈ 翻訳データを事前にダウンロード ◈ GameObjectが表示されるタイミングで動的に翻訳 ◈ コンポーネントから取得した日本語テキストから ハッシュ値を算出して対応した英語テキストに置き換え |表示・反映方法 ©2024 Magica
Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 63 -
©WFS public class LocalizeTextComponent : MonoBehaviour { private void OnEnable()
{ var component = GetComponent<TextMeshPro>(); // 元の日本語テキストをハッシュ化して英語テキストを取得 var hash = Hash128.Compute(component.text).ToString(); var trans = LocalizeTextMasterData.GetText(hash); // 英語テキストに置き換え component.text = trans; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 64 -
|表示・反映 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント -
65 -
◈ 変数を定義してインスペクター から入力する方法は使用禁止 ◈ 理由 翻訳対象となる日本語テキスト を抽出するのが難しい 翻訳処理を通しているかの 保証ができない |使用禁止ルール:
変数定義によるテキスト利用 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Unity UIテキストコンポーネント - 66 -
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 67 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 68 -
◈ 決められたクラス内に定義 ◈ プロパティ形式にして 翻訳メソッドを通す ◈ 決められたクラス以外で 定義することは禁止 |実装ルール ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 69 -
◈ 日本語テキストが定義されているクラスの プロパティ情報を取得 ◈ GetValue()を実行して日本語テキストを抽出 |抽出方法 ©2024 Magica Quartet/Aniplex,Magia Exedra
Project ©WFS C#ソースコード - 70 -
©WFS // LocalizeDefineTextクラスのプロパティを取得 var flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; var properties = typeof(LocalizeDefineText).GetProperties(flags); foreach (var property in properties) { // プロパティの値を取得してstringだった場合はリストに追加 switch (property.GetValue(null)) { case string str: texts.Add(str); break; case List<string> strList: texts.AddRange(strList); break; case IDictionary dict: var args = dict.GetType().IsGenericType ? dict.GetType().GetGenericArguments() : null; if (args != null && args.Length == 2 && args[1] == typeof(string)) { foreach (DictionaryEntry entry in dict) { texts.Add(entry.Key.ToString()); } } break; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 71 -
©WFS // LocalizeDefineTextクラスのプロパティを取得 var flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; var properties = typeof(LocalizeDefineText).GetProperties(flags); foreach (var property in properties) { // プロパティの値を取得してstringだった場合はリストに追加 switch (property.GetValue(null)) { case string str: texts.Add(str); break; case List<string> strList: texts.AddRange(strList); break; case IDictionary dict: var args = dict.GetType().IsGenericType ? dict.GetType().GetGenericArguments() : null; if (args != null && args.Length == 2 && args[1] == typeof(string)) { foreach (DictionaryEntry entry in dict) { texts.Add(entry.Key.ToString()); } } break; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 72 -
©WFS // LocalizeDefineTextクラスのプロパティを取得 var flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; var properties = typeof(LocalizeDefineText).GetProperties(flags); ◈ 取得したいプロパティの種類を指定 ◈ 条件に一致したクラスのプロパティ情報を取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 73 -
©WFS // LocalizeDefineTextクラスのプロパティを取得 var flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; var properties = typeof(LocalizeDefineText).GetProperties(flags); ◈ 取得したいプロパティの種類を指定 ◈ 条件に一致したクラスのプロパティ情報を取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 74 -
©WFS // LocalizeDefineTextクラスのプロパティを取得 var flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; var properties = typeof(LocalizeDefineText).GetProperties(flags); ◈ 取得したいプロパティの種類を指定 ◈ 条件に一致したクラスのプロパティ情報を取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 75 -
©WFS // プロパティの値を取得してstringだった場合はリストに追加 switch (property.GetValue(null)) { case string str: texts.Add(str);
break; ◈ プロパティを実行して値を取得 ◈ stringだった場合はリストに追加 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 76 -
©WFS // プロパティの値を取得してstringだった場合はリストに追加 switch (property.GetValue(null)) { case string str: texts.Add(str);
break; ◈ プロパティを実行して値を取得 ◈ stringだった場合はリストに追加 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 77 -
◈ 翻訳データを事前にダウンロード ◈ プロパティ利用時に動的翻訳 |表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS C#ソースコード - 78 -
©WFS public static class LocalizeDefineText { public static string PresentInBox
=> Translate("報酬はプレゼントボックスへ送られます"); public static string PresentConfirmText => Translate("以下のプレゼントを受け取りました。"); public static string AddGoldMaxOver => Translate("獲得できるA-Qチップが最大を越えています"); } public static string Translate(string orig) { // 日本語テキストに紐づいた英語テキストを取得 var hash = Hash128.Compute(orig).ToString(); return LocalizeTextMasterData.GetText(hash); } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 79 -
©WFS public static class LocalizeDefineText { public static string PresentInBox
=> Translate("報酬はプレゼントボックスへ送られます"); public static string PresentConfirmText => Translate("以下のプレゼントを受け取りました。"); public static string AddGoldMaxOver => Translate("獲得できるA-Qチップが最大を越えています"); } public static string Translate(string orig) { // 日本語テキストに紐づいた英語テキストを取得 var hash = Hash128.Compute(orig).ToString(); return LocalizeTextMasterData.GetText(hash); } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 80 -
©WFS public static string PresentInBox => Translate("報酬はプレゼントボックスへ送られます"); —----------------------------------------------------- // プロパティの利用
var data = new LoginBonusPopup.Data(text: PresentInBox); SystemUI.OpenPopup<LoginBonusPopup>(data); ◈ プロパティ形式にして翻訳メソッドを通す ◈ プロパティ利用時に翻訳を実行 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 81 -
©WFS public static string PresentInBox => Translate("報酬はプレゼントボックスへ送られます"); —----------------------------------------------------- // プロパティの利用
var data = new LoginBonusPopup.Data(text: PresentInBox); SystemUI.OpenPopup<LoginBonusPopup>(data); ◈ プロパティ形式にして翻訳メソッドを通す ◈ プロパティ利用時に翻訳を実行 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 82 -
©WFS public static string PresentInBox => Translate("報酬はプレゼントボックスへ送られます"); —----------------------------------------------------- // プロパティの利用
var data = new LoginBonusPopup.Data(text: PresentInBox); SystemUI.OpenPopup<LoginBonusPopup>(data); ◈ プロパティ形式にして翻訳メソッドを通す ◈ プロパティ利用時に翻訳を実行 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 83 -
©WFS public static string PresentInBox => Translate("報酬はプレゼントボックスへ送られます"); —----------------------------------------------------- // プロパティの利用
var data = new LoginBonusPopup.Data(text: PresentInBox); SystemUI.OpenPopup<LoginBonusPopup>(data); ◈ プロパティ形式にして翻訳メソッドを通す ◈ プロパティ利用時に翻訳を実行 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 84 -
©WFS public static string PresentInBox => Translate("報酬はプレゼントボックスへ送られます"); —----------------------------------------------------- // プロパティの利用
var data = new LoginBonusPopup.Data(text: PresentInBox); SystemUI.OpenPopup<LoginBonusPopup>(data); ◈ プロパティ形式にして翻訳メソッドを通す ◈ プロパティ利用時に翻訳を実行 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 85 -
|表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS C#ソースコード - 86
-
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 87 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 88 -
◈ シナリオはGoogle スプレッドシートで作成 ◈ UnityEditorでインポート ◈ ScriptableObjectの アセット形式で管理 |シナリオデータ管理 ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 89 -
◈ なし ScriptableObject内のセリフと 話者名の抽出が可能だった |実装ルール 抽出、表示・反映のための 特別な実装の必要はなし ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS シナリオデータ(ScriptableObject) - 90 -
◈ AssetDatabase.FindAssets($"t:{typeof(Class)}"); でシナリオ(ScriptableObject)のアセットを取得 ◈ シナリオ(ScriptableObject)に含まれる セリフ、話者名を抽出 |抽出方法 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS シナリオデータ(ScriptableObject) - 91 -
©WFS var texts = new List<string>(); var guids = AssetDatabase.FindAssets
($"t:{typeof(ScenarioScriptableObject}"); foreach (var guid in guids) { // シナリオ(ScriptableObject)をロード var path = AssetDatabase.GUIDToAssetPath(guid); var scenario = AssetDatabase.LoadAssetAtPath( path, typeof(ScenarioScriptableObject)); // シナリオ内のセリフをリストに追加 var serifs = scenario.GetSerifs(); texts.AddRange(serifs); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 92 -
©WFS var texts = new List<string>(); var guids = AssetDatabase.FindAssets
($"t:{typeof(ScenarioScriptableObject}"); foreach (var guid in guids) { // シナリオ(ScriptableObject)をロード var path = AssetDatabase.GUIDToAssetPath(guid); var scenario = AssetDatabase.LoadAssetAtPath( path, typeof(ScenarioScriptableObject)); // シナリオ内のセリフをリストに追加 var serifs = scenario.GetSerifs(); texts.AddRange(serifs); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 93 -
◈ シナリオ(ScriptableObject)に含まれる セリフ、話者名を英語テキストに置き換え ◈ JSON形式に変換して言語別にファイルを保存 ◈ 言語に応じたファイルをダウンロード |表示・反映方法 ©2024 Magica
Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 94 -
©WFS var guids = AssetDatabase.FindAssets($"t:{typeof(ScenarioScriptableObject)}"); foreach (var guid in guids)
{ var path = AssetDatabase.GUIDToAssetPath(guid); var scenario = AssetDatabase.LoadAssetAtPath(path, typeof(ScenarioScriptableObject)) as ScenarioScriptableObject; var serifs = scenario.GetSerifs(); for (int lineNumber = 1; 1 <= serifs.Count; lineNumber++) { var serif = serifs[lineNumber - 1]; // シナリオ内のセリフを英語テキストに置き換え var translatedSerif = Translate(serif); scenario.Replace(lineNumber, translatedSerif); } // 英語用のシナリオをJSONファイルとして保存 var json = JsonUtility.ToJson(scenario, true); using StreamWriter writer = new StreamWriter(filePath); writer.Write(json); } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 95 -
©WFS var guids = AssetDatabase.FindAssets($"t:{typeof(ScenarioScriptableObject)}"); foreach (var guid in guids)
{ var path = AssetDatabase.GUIDToAssetPath(guid); var scenario = AssetDatabase.LoadAssetAtPath(path, typeof(ScenarioScriptableObject)) as ScenarioScriptableObject; var serifs = scenario.GetSerifs(); for (int lineNumber = 1; 1 <= serifs.Count; lineNumber++) { var serif = serifs[lineNumber - 1]; // シナリオ内のセリフを英語テキストに置き換え var translatedSerif = Translate(serif); scenario.Replace(lineNumber, translatedSerif); } // 英語用のシナリオをJSONファイルとして保存 var json = JsonUtility.ToJson(scenario, true); using StreamWriter writer = new StreamWriter(filePath); writer.Write(json); } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 96 -
©WFS var guids = AssetDatabase.FindAssets($"t:{typeof(ScenarioScriptableObject)}"); foreach (var guid in guids)
{ var path = AssetDatabase.GUIDToAssetPath(guid); var scenario = AssetDatabase.LoadAssetAtPath(path, typeof(ScenarioScriptableObject)) as ScenarioScriptableObject; var serifs = scenario.GetSerifs(); for (int lineNumber = 1; 1 <= serifs.Count; lineNumber++) { var serif = serifs[lineNumber - 1]; // シナリオ内のセリフを英語テキストに置き換え var translatedSerif = Translate(serif); scenario.Replace(lineNumber, translatedSerif); } // 英語用のシナリオをJSONファイルとして保存 var json = JsonUtility.ToJson(scenario, true); using StreamWriter writer = new StreamWriter(filePath); writer.Write(json); } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 97 -
©WFS // 英語用のシナリオをJSONファイルとして保存 var json = JsonUtility.ToJson(scenario, true); using StreamWriter
writer = new StreamWriter(filePath); writer.Write(json); ◈ ScriptableObjectからJSONへの変換 ◈ JSONファイルとして保存 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 98 -
©WFS // 英語用のシナリオをJSONファイルとして保存 var json = JsonUtility.ToJson(scenario, true); using StreamWriter
writer = new StreamWriter(filePath); writer.Write(json); ◈ ScriptableObjectからJSONへの変換 ◈ JSONファイルとして保存 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 99 -
©WFS // JSONファイルからScriptableObjectを復元 var ins = ScriptableObject .CreateInstance<ScenarioScriptableObject>(); JsonUtility.FromJsonOverwrite(json, ins);
◈ JSONからScriptableObjectへの変換 ◈ ScriptableObjectインスタンスにオーバーライト ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 100 -
©WFS // JSONファイルからScriptableObjectを復元 var ins = ScriptableObject .CreateInstance<ScenarioScriptableObject>(); JsonUtility.FromJsonOverwrite(json, ins);
◈ JSONからScriptableObjectへの変換 ◈ ScriptableObjectインスタンスにオーバーライト ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 101 -
©WFS // JSONファイルからScriptableObjectを復元 var ins = ScriptableObject .CreateInstance<ScenarioScriptableObject>(); JsonUtility.FromJsonOverwrite(json, ins);
◈ JSONからScriptableObjectへの変換 ◈ ScriptableObjectインスタンスにオーバーライト ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject) - 102 -
|表示・反映方法 scenario0001.json scenario0001_en-Latn.json ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS シナリオデータ(ScriptableObject)
- 103 -
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 104 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 105 -
◈ File Signature ◈ 空行 ◈ キュータイミング ◈ 字幕テキスト |HLS
.vttファイル (WebVTTフォーマット) ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 106 -
◈ なし 字幕ファイルのフォーマットから テキスト部分の抽出が可能 |実装ルール 抽出、表示・反映のための 特別な実装の必要はなし ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS ストリーミング動画の字幕 - 107 -
◈ .vttファイルをロード ◈ 「File Signature」「空行」「キュータイミング」 を除いた「字幕テキスト」部分を抽出 |抽出方法 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS ストリーミング動画の字幕 - 108 -
©WFS var filePaths = Directory.GetFiles(directoryPath, "*.vtt", SearchOption.AllDirectories); foreach (var filePath
in filePaths) { var content = File.ReadAllText(filePath); var lines = content.Split(new[] {"¥r¥n", "¥r", "¥n"}, StringSplitOptions.None).ToList(); foreach (var line in lines) { // File Signature or 空行 or キュータイミング はスキップ var pat = @"^¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*-->¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*$"; if (line == "WEBVTT" || string.IsNullOrEmpty(line) || Regex.IsMatch(line, pat)) { continue; } // 字幕テキストを取得 texts.Add(line); } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 109 -
©WFS var filePaths = Directory.GetFiles(directoryPath, "*.vtt", SearchOption.AllDirectories); foreach (var filePath
in filePaths) { var content = File.ReadAllText(filePath); var lines = content.Split(new[] {"¥r¥n", "¥r", "¥n"}, StringSplitOptions.None).ToList(); foreach (var line in lines) { // File Signature or 空行 or キュータイミング はスキップ var pat = @"^¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*-->¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*$"; if (line == "WEBVTT" || string.IsNullOrEmpty(line) || Regex.IsMatch(line, pat)) { continue; } // 字幕テキストを取得 texts.Add(line); } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 110 -
◈ .vttファイルをロード ◈ 字幕テキストを英語テキストに置き換え ◈ 置き換えた.vttファイルを別ファイルに保存 |表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS ストリーミング動画の字幕 - 111 -
©WFS var content = File.ReadAllText(filePath); var lines = content.Split(new[] {"¥r¥n",
"¥r", "¥n"}, StringSplitOptions.None).ToList(); foreach (var line in lines) { // File Signature or 空行 or キュータイミング var pat = @"^¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*-->¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*$"; if (line == "WEBVTT" || string.IsNullOrEmpty(line) || Regex.IsMatch(line, pat)) { texts.Add(line); continue; } // 字幕テキストは英語テキストに置き換え var translatedLine = Translate(line); texts.Add(translatedLine); } // 英語テキストをファイルに保存 File.WriteAllText(filePath, string.Join("¥n", texts)); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 112 -
©WFS var content = File.ReadAllText(filePath); var lines = content.Split(new[] {"¥r¥n",
"¥r", "¥n"}, StringSplitOptions.None).ToList(); foreach (var line in lines) { // File Signature or 空行 or キュータイミング var pat = @"^¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*-->¥s*(¥d{1,2}:)?¥d{2}:¥d{2}¥.¥d{3}¥s*$"; if (line == "WEBVTT" || string.IsNullOrEmpty(line) || Regex.IsMatch(line, pat)) { texts.Add(line); continue; } // 字幕テキストは英語テキストに置き換え var translatedLine = Translate(line); texts.Add(translatedLine); } // 英語テキストをファイルに保存 File.WriteAllText(filePath, string.Join("¥n", texts)); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕 - 113 -
|表示・反映方法 movie0001.vtt movie0001_en-Latn.vtt ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ストリーミング動画の字幕
- 114 -
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 115 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 116 -
◈ なし Spannerのスキーマ情報から 日本語テキストを抽出可能 |実装ルール 抽出、表示・反映のための 特別な実装の必要はなし ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS マスターデータ - 117 -
◈ テーブルのスキーマ情報を取得 ◈ STRING(XXX)で定義されているカラムの値を抽出 |抽出方法 ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS マスターデータ - 118 -
©WFS $database = (new SpannerClient())->connect('instanceId', 'databaseId'); // STRING型カラムを取得 $indexColumns =
$database->execute(" SELECT column_name FROM information_schema.columns WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); $stringColumns = array_column($indexColumns, 'column_name'); // 日本語版テーブルからSTRING型カラムの値をリストに追加 $records = $database->execute("SELECT * FROM SkillMst"); foreach ($records as $record) { foreach ($stringColumns as $column) { $texts[] = $record[$column]; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 119 -
©WFS $database = (new SpannerClient())->connect('instanceId', 'databaseId'); // STRING型カラムを取得 $indexColumns =
$database->execute(" SELECT column_name FROM information_schema.columns WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); $stringColumns = array_column($indexColumns, 'column_name'); // 日本語版テーブルからSTRING型カラムの値をリストに追加 $records = $database->execute("SELECT * FROM SkillMst"); foreach ($records as $record) { foreach ($stringColumns as $column) { $texts[] = $record[$column]; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 120 -
©WFS // STRING型カラムを取得 $indexColumns = $database->execute(" SELECT column_name FROM information_schema.columns
WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); ◈ テーブルのスキーマ情報からSTRING型カラムを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 121 -
©WFS // STRING型カラムを取得 $indexColumns = $database->execute(" SELECT column_name FROM information_schema.columns
WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); ◈ テーブルのスキーマ情報からSTRING型カラムを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 122 -
©WFS // STRING型カラムを取得 $indexColumns = $database->execute(" SELECT column_name FROM information_schema.columns
WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); ◈ テーブルのスキーマ情報からSTRING型カラムを取得 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 123 -
◈ 日本語マスターデータを取得 ◈ 日本語テキストを英語テキストに置き換え ◈ 英語用のテーブルに反映 |表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS マスターデータ - 124 -
©WFS $database = (new SpannerClient())->connect('instanceId', 'databaseId'); // STRING型カラムを取得 $indexColumns =
$database->execute(" SELECT column_name FROM information_schema.columns WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); $stringColumns = array_column($indexColumns, 'column_name'); $records = $database->execute("SELECT * FROM SkillMst"); foreach ($records as $rowIndex => $record) { foreach ($stringColumns as $column) { // 日本語テーブルのSTRING型カラムを英語テキストに置き換え $records[$rowIndex][$column] = translate($value); } } // 英語テーブルを更新 $database->insertOrUpdateBatch(SkillMst_en_Latn, [$records]); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 125 -
©WFS $database = (new SpannerClient())->connect('instanceId', 'databaseId'); // STRING型カラムを取得 $indexColumns =
$database->execute(" SELECT column_name FROM information_schema.columns WHERE table_name = 'SkillMst' AND spanner_type LIKE 'STRING%'"); $stringColumns = array_column($indexColumns, 'column_name'); $records = $database->execute("SELECT * FROM SkillMst"); foreach ($records as $rowIndex => $record) { foreach ($stringColumns as $column) { // 日本語テーブルのSTRING型カラムを英語テキストに置き換え $records[$rowIndex][$column] = translate($value); } } // 英語テーブルを更新 $database->insertOrUpdateBatch(SkillMst_en_Latn, [$records]); ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS マスターデータ - 126 -
|表示・反映方法 table: SkillMst table: SkillMst_en_Latn ©2024 Magica Quartet/Aniplex,Magia Exedra Project
©WFS マスターデータ - 127 -
|実装対象 ◈ Unity UIテキストコンポーネント ◈ C#ソースコード ◈ シナリオデータ(ScriptableObject) ◈ ストリーミング動画の字幕
◈ マスターデータ ◈ 設定ファイル(YAML) 実装ルール 抽出方法 表示・反映方 法 実装 - 128 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 129 -
◈ なし 「ひらがな、カタカナ、漢字」だ った場合に抽出 |実装ルール 抽出、表示・反映のための 特別な実装の必要はなし ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS 設定ファイル(YAML) - 130 -
◈ 正規表現で「ひらがな」「カタカナ」「漢字」が 含まれているかどうかを判定 ◈ 含まれている場合は翻訳対象として抽出 |抽出方法 ©2024 Magica Quartet/Aniplex,Magia Exedra
Project ©WFS 設定ファイル(YAML) - 131 -
©WFS $yamlData = yaml_parse_file($yamlPath); // 多次元配列を1次元配列にフラット化 $data = $this->flatten($yamlData); foreach
($data as $key => $value) { // yamlの値が翻訳対象の場合は日本語テキストを取得 $pattern = '/[ぁ-ん]+|[ァ-ヴー]+|[一-龠]/u'; if (preg_match($pattern, $value)) { $texts[] = $value; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 132 -
©WFS $yamlData = yaml_parse_file($yamlPath); // 多次元配列を1次元配列にフラット化 $data = $this->flatten($yamlData); foreach
($data as $key => $value) { // yamlの値が翻訳対象の場合は日本語テキストを取得 $pattern = '/[ぁ-ん]+|[ァ-ヴー]+|[一-龠]/u'; if (preg_match($pattern, $value)) { $texts[] = $value; } } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 133 -
◈ yamlデータ取得時に翻訳を動的に実行 ◈ yamlデータに「ひらがな」「カタカナ」「漢字」が 含まれている場合は英語テキストに置き換え |表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia Exedra
Project ©WFS 設定ファイル(YAML) - 134 -
©WFS private function translateRecursive($yamlData) { foreach ($yamlData as $index =>
$data) { if (is_array($data)) { $yamlData[$index] = $this->translateRecursive($data); } else if (preg_match('/[ぁ-んァ-ヴー一-龠]/u', $data)) { // yamlの値が翻訳対象の場合は翻訳テキストに置き換え $hashKey = md5($data); $yamlData[$index] = $this->translate($hashKey); } } return $yamlData; } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 135 -
©WFS private function translateRecursive($yamlData) { foreach ($yamlData as $index =>
$data) { if (is_array($data)) { $yamlData[$index] = $this->translateRecursive($data); } else if (preg_match('/[ぁ-んァ-ヴー一-龠]/u', $data)) { // yamlの値が翻訳対象の場合は翻訳テキストに置き換え $hashKey = md5($data); $yamlData[$index] = $this->translate($hashKey); } } return $yamlData; } ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 136 -
|表示・反映方法 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 設定ファイル(YAML) - 137
-
◈ TextMeshProがアタッチされているGameObjectに 翻訳用のコンポーネントをアタッチ ◈ C#のソースコード上で日本語テキストを使いたい 場合は決められたクラス内に定義 ◈ 決められたルール以外での実装は行わない |実装まとめ ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS 実装 - 138 -
◈ TextMeshProがアタッチされているGameObjectに 翻訳用のコンポーネントをアタッチ ◈ C#のソースコード上で日本語テキストを使いたい 場合は決められたクラス内に定義 ◈ 決められたルール以外での実装は行わない |実装まとめ ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS 実装 - 139 -
◈ TextMeshProがアタッチされているGameObjectに 翻訳用のコンポーネントをアタッチ ◈ C#のソースコード上で日本語テキストを使いたい 場合は決められたクラス内に定義 ◈ 決められたルール以外での実装は行わない |実装まとめ ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS 実装 - 140 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 141 -
開発チームと翻訳チームの分業 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 142 -
|使用技術 ◈ Googleスプレッドシート ◈ Google Apps Script ◈ Jenkins ◈
Slack 開発チームと翻訳チームの分業 - 143 - ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
開発チーム 翻訳チーム Developer Translator Repository Management Tool Text + Meta
Extractor Reflector ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 開発チームと翻訳チームの分業 - 144 -
◈ スラッシュ( / )から始まる特定のコマンドを入力、 送信を行うことでアプリを呼び出すことが可能 ◈ 引数を指定してアプリに渡すこともできる |Slack Slash Commandsとは
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slack Slash Commands - 145 -
◈ 非エンジニアには馴染みのないコマンド ◈ コマンド実行時の引数指定が複雑 |Slack Slash Commandsの課題点 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS Slack Slash Commands - 146 -
◈ 定型作業を自動化 ◈ 様々なイベントトリガーから 起動可能 ◈ ステップを繋げて作業を完遂 |Slackワークフローとは ©2024 Magica
Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 147 -
◈ ブランチ (環境) ◈ Arg1: テキスト or シナリオ ◈ Arg2:
未翻訳 or 翻訳済み ◈ Arg3: 新規・追加 or 修正差分 ◈ その他絞り込み |Slackワークフロー ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 148 -
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 開発チームと翻訳チームの分業 - 149 -
◈ ステップ1: 「情報をフォームで収集する」 ◈ ステップ2: 「スプレッドシートに追加する」 ◈ ステップ3: 「チャンネルへメッセージを 送信する」
|Slackワークフロー設定 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 150 -
◈ メニューから「トリガー」を追加 ◈ 関数: Jenkinsに送信する関数 ◈ ソース: 「スプレッドシートから」 ◈ 種類:
「変更時」 |Googleスプレッドシート&GoogleAppsScript設定 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 151 -
◈ Generic Webhook Trigger Plugin ◈ 指定されたURLにPOST送信 ◈ ペイロードはVariableで指定した 変数に格納される
◈ pipelineでJSONデータをパース して任意のジョブを実行 |Jenkins設定 ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 152 -
開発チーム 翻訳チーム Developer Translator Repository Management Tool Text + Meta
Extractor Reflector ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS Slackワークフロー - 153 -
◈ Slackワークフローを導入したことで 大半の作業が翻訳チームのみで完結 ◈ 翻訳依頼や反映など作業に関するやり取りや 相手の対応待ちといった時間を大幅に削減 ◈ 自分達の作業に集中できる時間が増加し品質が向上 |開発チームと翻訳チームの分業まとめ ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS 開発チームと翻訳チームの分業 - 154 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 155 -
ローカライズで困ったこと ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 156 -
|複数翻訳パターン ◈ ひとつの日本語に別々の英訳 日本語: おすすめ (提案, 最適化) 英語 : Suggest,
Optimize ◈ 利用場面に応じた表記 日本語 : こころの器 英語 : Heartphial タイトル表題: Heartphials ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ローカライズで困ったこと - 157 -
|複数翻訳パターン ◈ 日本語版での英字表記の翻訳 日本語: バトル, Battle 英語 : Battle, Battle
◈ 短縮系 英語 : Attack, Defense 短縮系: ATK, DEF ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ローカライズで困ったこと - 158 -
|複数翻訳パターン ◈ 日本語版での英字表記の翻訳 日本語: バトル, Battle 英語 : Battle, Battle
Battler ◈ 短縮系 英語 : Attack, Defense 短縮系: ATK, DEF ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ローカライズで困ったこと - 159 -
|ダミーテキスト ◈ 無駄な翻訳作業が発生 ◈ 用途不明なテキストの混入 Unity UIコンポーネントで多数発生 ©2024 Magica Quartet/Aniplex,Magia
Exedra Project ©WFS ローカライズで困ったこと - 160 -
|ダミーテキスト [解決案] ◈ ダミーだとわかるテキストを利用 先頭に「仮」をつけるなど ◈ 実装者の負担にもなるので実現には プロダクトの理解が必要 ©2024 Magica
Quartet/Aniplex,Magia Exedra Project ©WFS ローカライズで困ったこと - 161 -
|UI文字数オーバー ◈ 基本的に英語のほうが文字数が多い ◈ 2〜5文字程度の熟語も英語にすると 5〜10倍の文字数になることも ◈ 日本語は主語や目的語を省くことも 多いが英語では明記する必要がある ©2024
Magica Quartet/Aniplex,Magia Exedra Project ©WFS ローカライズで困ったこと - 162 -
◈ 課題と目標 ◈ 設計 ◈ 実装 ◈ 開発チームと翻訳チームの分業 ◈ ローカライズで困ったこと
◈ まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS 目次 - 163 -
まとめ ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS - 164 -
©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ❖ 忙しすぎる開発チームの負担を増やしてはいけない ❖ テキストだけでは自然な翻訳は難しい
❖ 抜け漏れをなくすために実装ルールや制限を上手に使う ❖ 自動化するだけでは不十分、ユーザーフレンドリーを ❖ 同期は小さく頻繁に! ❖ 無理な「協力」より自律した上での「協創」を まとめ - 165 -
ご清聴ありがとうございました ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS
WFSセッション一覧 WFSはCEDEC 2025で全8セッション登壇いたします ©2024 Magica Quartet/Aniplex,Magia Exedra Project ©WFS ©WFS
Developed by WRIGHT FLYER STUDIOS © VISUAL ARTS/Key 7/22 第4会場 13:40-14:40 レギュラーセッション LLM翻訳ツールの開発と 海外のお客様対応等への社内導入事例 郡司 匡弘 / 松井 望 / 小野 幸人 BP 7/23 第1会場 09:30-10:30 レギュラーセッション ヘブンバーンズレッドにおける、 世界観を活かしたミニゲーム企画の作り 方 菊岡 大夢 GD 7/23 第1会場 10:50-11:50 レギュラーセッション ヘブンバーンズレッドの レンダリングパイプライン刷新 野口 顕弘 ENG 7/23 第2会場 13:20-14:20 レギュラーセッション 「魔法少女まどか☆マギカ Magia Exedra」の グローバル展開を支える、開発チームと翻訳チームの 「意識しない協創」を実現するローカライズシステム 原田 大志 / 篠原 功 PRD 7/23 第2会場 13:20-14:20 レギュラーセッション 「魔法少女まどか☆マギカ Magia Exedra」 の必殺技演出を徹底解剖! -キャラクターの魅力を最大限にファンに届けるためのこだわり- 新谷 雄輝 / 金子 俊太朗 / 佐々木 文哉 VA 7/24 第5会場 15:00-15:25 ショートセッション ライブサービスゲームQAのパフォーマ ンス検証による品質改善の取り組み 小野 粋哉 / 勅使川原 大輔 BP 7/24 第5会場 18:00-18:25 ショートセッション ヒューリスティック評価を用いた ゲームQA実践事例 山本 幸寛 PRD 7/24 第8会場 18:00-18:25 ショートセッション 「魔法少女まどか☆マギカ Magia Exedra」での負荷試験の実践と学び 悦田 潤哉 ENG
©WFS