Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
「魔法少女まどか☆マギカ Magia Exedra」のグローバル展開を支える、開発チームと翻訳...
Search
gree_tech
PRO
September 01, 2025
Technology
1.9k
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
1on1、進捗確認だけになっていませんか?〜明日から使えるTips〜
gree_tech
PRO
0
7
AI画像認識を活用したゲーム内決済処理検証の自動化
gree_tech
PRO
0
980
長期運営で肥大化したExcelマスターデータの解消に向けた移行事例
gree_tech
PRO
0
1.1k
ゲームにおけるメディアミックス展開との向き合い方 -ヘブンバーンズレッドのメディアミックス企画の立て方-
gree_tech
PRO
0
1.1k
課題発見から始めるプロダクト密着型TA組織立ち上げ 〜小規模TAチームが3Dアセットの生産性を倍にした方法〜
gree_tech
PRO
0
1.1k
属人化を叩き割れ!「制作加速」と「安定化」の矛盾を打破する:8年目プロダクトが辿り着いた「ミスが起こり得ない」アセット制作フローの全貌
gree_tech
PRO
0
970
20年続く長寿タイトルが、15年目にして売上を伸ばせた理由
gree_tech
PRO
0
1.6k
我々はどう生きるか
gree_tech
PRO
2
1.1k
変わるもの、変わらないもの :OSSアーキテクチャで実現する持続可能なシステム
gree_tech
PRO
0
5.6k
Other Decks in Technology
See All in Technology
GitHub Agentic Workflows を触ってみる
htkym
2
820
Lambda MicroVMsが分からなすぎたので使い所を1から考えてみた
tsukuboshi
1
110
AIに任せた品質は、誰が見立てるのか - AI時代のテストマネジメント
nakanao
3
3.2k
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
16
120k
手を動かして実感する、Kiro が変える開発体験
inariku
0
400
「今盗んで、後で解く」に備える ― AWSのポスト量子暗号入門
yama3133
2
260
TiDBファミリーにDWHが新登場!! TiDB最新情報 / TiDB update 202609
yoshiakiyamasaki
0
140
AI感のないAWS構成図をAIエージェントに描かせたい!
sagochiko
2
470
Kiro Meetup #8 Kiro アップデート (2026/3/21〜2026/9/24)
katzueno
1
290
AI駆動開発で仕様はどこまで書くべきか? ― 人とAIの責務境界から考える開発プロセスの実践
takahiromatsui
1
150
Argo CDとAtlantisで実現するインフラ管理のセルフサービス化──小規模SREチームで支えるプラットフォーム
cassius7
0
220
Mastering Agentic Development: Harness Engineering for Effective Coding Agents
konippi
3
610
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
41
2.7k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
680
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
58k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The Spectacular Lies of Maps
axbom
PRO
1
1k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
690
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
440
Producing Creativity
orderedlist
PRO
348
41k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
2k
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