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
2k
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
多彩なデータに負けないデータ分析パイプライン構築
gree_tech
PRO
0
40
AI駆動で挑むプロダクト刷新 ── アーキテクチャ設計とワークフロー最適化の実践事例 ──
gree_tech
PRO
0
36
『ヘブンバーンズレッド』のリグレッションテストにおけるストーリーの自動プレイ
gree_tech
PRO
0
64
REALITYにTAチームができるまでの軌跡〜ミッション:ガチャ本数5倍に対応せよ!〜
gree_tech
PRO
0
24
イベント続きでもミスらないライブサービスゲーム公式サイト運用システム開発
gree_tech
PRO
0
29
20年間 SNS サービス「GREE」 を支えた技術の刷新と、長期運用サービスの Kubernetes へのリアーキテクト
gree_tech
PRO
0
80
グラフ基盤ワークフローで実現するLLMによる自動ゲームプレイ
gree_tech
PRO
0
50
AIエージェントに脆弱性診断の仕事を奪われかけた ークローリングからコード全診断までの限界突破ー
gree_tech
PRO
0
37
アバター使っておもろいものを作り続けたい
gree_tech
PRO
0
33
Other Decks in Technology
See All in Technology
日本語に強いオープンな大規模言語モデル「LLM-jp」
odashi
5
1.2k
トークン化預金DCJPYの取り組みと活用ユースケース
finengine
0
170
Claude の新機能を、 出た日に触って社内に届けるまで
rfdnxbro
1
190
脆弱性診断って、何をしているの? 〜弱点を見つけて、会社の改善につなげるまで〜
keeg
0
930
Webの常識が通じないHTMLメールのダークモード
teamlab
PRO
0
140
Rustuna: Rustによる高速なOptuna実装
pfn
PRO
0
200
Temporal 最高!でも改めて「日付」について調べたら人類の負債すぎる件
riyaamemiya
0
180
会社説明資料
persolserverworks
0
9.8k
サブエージェントと Claude Mods で作る独自 Advisor ツールの話
kenichiodo
2
170
DGX Spark でどこまでハッキングができるのか?
kash1064
3
1.4k
フィンテック養成勉強会#68
finengine
0
150
「Docker Desktop不要の時代」は 来たのか?
ueponx
0
830
Featured
See All Featured
The SEO identity crisis: Don't let AI make you average
varn
0
590
Intergalactic Javascript Robots from Outer Space
tanoku
273
28k
For a Future-Friendly Web
brad_frost
184
10k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
540
Large-scale JavaScript Application Architecture
addyosmani
516
110k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
4
1.2k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
43k
Making Projects Easy
brettharned
120
6.8k
sira's awesome portfolio website redesign presentation
elsirapls
0
460
Designing Powerful Visuals for Engaging Learning
tmiket
1
610
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
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