Radical Cobblemon Trainersの日本語化リソースパックを作った話


概要

最近Cobblemonにハマっていて、その中のside modであるRadical Cobblemon Trainers(以後、RCTと呼ぶ)の日本語化リソースパック&データパックを作った。

Cobblemon is 何

Cobblemonとは、簡単に言うとマイクラの世界をポケモンの世界に変えるMod。似たModにPixelmonがあるが、 こちらは完全にポケモンの世界と置き換えるコンセプトなのに対し、Cobblemonはマイクラの標準の機能は維持しつつ、ポケモンの世界観をいい感じにフュージョンする感じのModだ。

RCTはそのCobblemonの世界にNPCのトレーナーを追加するMod。

やっていく

ModrinthからRCTをダウンロードすると、以下のような構成になっている。

.
|-- assets
|   `-- rctmod
|       `-- lang
|           `-- en_us.json
`-- data
    `-- rctmod
        `-- trainers
            |-- ace_trainer_abel_04a5.json
            |-- ace_trainer_alexa_0194.json
            `-- ...

lang配下にトレーナーのセリフ。trainers配下にそれぞれのトレーナーのconfig(名前とか)が入っているので、これらを頑張って翻訳していく。

langのほうは雑にAIに翻訳を投げて、ja_jp.jsonを生成した。一部ポケモン由来の固有名詞とかはいい感じにwebを参照させて、名前のマッピングを行った。しっかりチェックしてないのでもしかしたら誤訳があるかも。ただ意味はある程度通じるレベルにはなってると思う。

trainersのほうが面倒で、それぞれのtrainerファイルは以下のような構造になっている。

fisherman_andrew_00e9.json
{
  "name": "Fisherman Andrew",
  "team": [
    {
      "species": "froslass",
      "gender": "FEMALE",
      "level": 88,
      "nature": "timid",
      "ability": "snowcloak",
      "moveset": [
        "auroraveil",
        "blizzard",
        "shadowball",
        "freezedry"
      ],
      "ivs": {
        "hp": 31,
        "atk": 31,
        "def": 31,
        "spa": 31,
        "spd": 31,
        "spe": 31
      },
...

肩書+名前みたいな感じ。はじめは名前を全て日本語名にマッピングしようと思ったが、ポケモンWikiを見ても、見つからない名前とかがあるので全部マッピングするのは諦めた。

アプローチとしては、各trainersファイルからnameの箇所を抜き出し、肩書き名+名前に分解。肩書き名のマッピングを記載したtitle_map.jsonに該当の肩書きがあれば書き換え、そうでなければskipする。

title_map.jsonを作る前準備として肩書き一覧は事前に以下のように、jqで抜いておく。

jq -r '
  def extract_title:
    split(" ")
    | .[0:-1]
    | join(" ");
  if .identity? then
    .identity | extract_title
  elif (.name | test("&| and ")) | not then
    .name | extract_title
  else
    empty
  end
' *.json | sort -u > title_candidates.txt

すると、以下のようなファイルができるのでこれをAIに投げていい感じの翻訳をつける。

title_candidates.txt
Ace Trainer
Aroma Lady
Artist
Battle Girl
Beauty
Biker
Bird Keeper
Black Belt
...

目視で確認して、イケてない訳は手動で修正する。

title_map.json
{
  "Ace Trainer": "エーストレーナー",
  "Aroma Lady": "アロマなおねえさん",
  "Artist": "げいじゅつか",
  "Battle Girl": "バトルガール",
  "Beauty": "おとなのおねえさん",
  "Biker": "ぼうそうぞく",
  "Bird Keeper": "とりつかい",
  "Black Belt": "からておう",
... 

後はこのファイルをもとに、trainers配下のjsonファイルの肩書き部分を置き換える。

localize.py
...
def localize_name(original_name: str, trainer: dict) -> str | None:

    # ---- 固有名 完全一致 ----
    if original_name in PROPER_NAME_MAP:
        return PROPER_NAME_MAP[original_name]

    # ---- 正規化(Swimmer♂ 等)----
    norm_title, norm_gender, normalized = normalize_name(original_name) # 後述

    # ---- title 決定 ----
    if norm_title:
        title = norm_title
        rest = normalized
    else:
        tokens = original_name.split()
        title = None
        rest = None

        for i in range(len(tokens), 0, -1):
            candidate = " ".join(tokens[:i])
            if candidate in TITLE_MAP:
                title = candidate
                rest = " ".join(tokens[i:])
                break

        if not title:
            print(f"[skip] title not found: {original_name}")
            return None

    title_entry = TITLE_MAP.get(title)
    ...

一部、肩書きのみでは訳を当てられないパターンがあることに後から気づいた。たとえばSwimmerなどはgenderを見ないとだめ。

{
  "name": "Swimmer Evan",
  "team": [
    {
      "species": "golduck",
      "gender": "FEMALE",

そのため、title_mapのほうで以下のように定義しておき、

  "Swimmer": {
    "FEMALE": "ビキニのおねえさん",
    "MALE": "かいパンやろう"
  },

性別で分岐するようにした。

localize.py
...
def normalize_name(name: str):
    """
    Swimmer♂ David -> ("Swimmer", "MALE", "David")
    Tuber♀ Alice   -> ("Tuber", "FEMALE", "Alice")
    """
    m = re.match(r"^(Swimmer|Tuber)(♂|♀)\s+(.*)$", name)
    if m:
        title, mark, rest = m.groups()
        gender = "MALE" if mark == "♂" else "FEMALE"
        return title, gender, rest
    return None, None, name


def get_trainer_gender(trainer: dict):
    for p in trainer.get("team", []):
        g = p.get("gender")
        if g in ("MALE", "FEMALE"):
            return g
    return None

def localize_name(original_name: str, trainer: dict) -> str | None:

    # ---- 固有名 完全一致 ----
    if original_name in PROPER_NAME_MAP:
        return PROPER_NAME_MAP[original_name]

    # ---- 正規化(Swimmer♂ 等)----
    norm_title, norm_gender, normalized = normalize_name(original_name)

    # ---- title 決定 ----
    if norm_title:
        title = norm_title
        rest = normalized
    else:
        tokens = original_name.split()
        title = None
        rest = None

        for i in range(len(tokens), 0, -1):
            candidate = " ".join(tokens[:i])
            if candidate in TITLE_MAP:
                title = candidate
                rest = " ".join(tokens[i:])
                break

        if not title:
            print(f"[skip] title not found: {original_name}")
            return None

    title_entry = TITLE_MAP.get(title)

    # ---- 性別分岐 ----
    if isinstance(title_entry, dict):
        gender = norm_gender or get_trainer_gender(trainer)
        if not gender or gender not in title_entry:
            print(f"[skip] gender unknown: {original_name}")
            return None
        jp_title = title_entry[gender]
    else:
        jp_title = title_entry
    ...

あと、以下のようなユニークキャラは肩書きがないので別途名前をマッピングしたファイルを用意することにした。カスミって英語だとMistyなんだ。

{
  "name": "Misty",
  "identity": "Leader Misty",
...
proper_name_map.json
{
  "Brock": "タケシ",
  "Misty": "カスミ",
  "Lt. Surge": "マチス",
...

localize.py実行時、マッピングしたファイルに名前が見つからない場合は以下のようにskipと表示されるので、Mod本体のバージョンが上がっても、見つからない肩書きだけtitle_map.jsonに追加した後、localize.pyを実行するだけでリソースパックが作れる状態にできた。

[skip] title not found: Youngster Chad

導入方法

以下で公開してます。

Releases · 4nm1tsu/radical-cobblemon-trainers-ja

Releases · 4nm1tsu/radical-cobblemon-trainers-ja

Radical Cobblemon Trainersの日本語化パック. Contribute to 4nm1tsu/radical-cobblemon-trainers-ja development by creating an account on GitHub.

github.com favicon github.com

対象バージョンのパック(zipファイル)をダウンロードしたら、.minecraft/resourcepacksフォルダに入れた後、ゲーム内でリソースパックを有効化してください。

また、ワールドのdatapacksにも同じzipファイルを導入後、/datapack enableコマンドで有効化してください。/reloadコマンドでin-gameでの読み込みが可能です。

誤訳があったらIssueで知らせてください。

以上。