自分を活かして 相手を活かして 今を活かす

【GAS】カタカナ表記をパスポートでも使えるヘボン式ローマ字に変換するスクリプト(2025年9月改修)

こんにちは、おかちゃんせんせいです!

これまで番外編として、業務効率化のために作業を自動化する話を何回かピックアップしました。

【解決】Excel 2019 for MacのVBAにて、複数ファイルを選択する方法 【業務効率化】Windows版Excel VBAでChromeを自動操作して、Webスクレイピングする方法(初期設定編) 【解決】Power AutomateでExcel Onlineにある日付と実行日が一致する場合に、処理を実行するようにする

もちろん自動化すればいいってものでもないですが、自動化することでミスや作業時間を減らし、他に生産的な仕事に時間を割くことができるようになることもあります。

自動化という意味では、プログラムを組まなくてもGoogleスプレッドシートの関数だけで、なんとかやりくりする方法もあります。

今回は、Googleスプレッドシートでマクロを組む際に使用する開発言語Google Apps Script(GAS)についての記事を特集します。

◆お知らせ◆

【まとめ記事】

現在、これまで書いてきた記事をテーマ別にまとめています。

詳しくはこちらから

GASで実現したい作業

メールアドレス作成の際に必要となるアカウント名

今回GASで実現したいことは、
メールアドレス作成の際に必要となるアカウント名を、特定のデータがあれば自動作成すること
です。

本当はそれ以外にもやりたいことはありますが、まずは上述の部分であれば一定のルールのもとで自動化できると考えました。

今のところは、フリガナを見てローマ字に変換して、メールアドレスのアカウント名にしていましたが……

ローマ字で表現するときに、
「H」なのか「F」なのか?
「N」なのか「M」なのか?
「Z」なのか「J」なのか?
よくわからなくなってしまうことがあるので、そんなときには下記サイトにかなり助けられました。

ただ、毎回サイト開いてコピペするのも面倒だし、同じ作業の繰り返し、かつ、一定のルールがあるから自動化できないかと思い、ネットを検索。

カタカナではないですが、GASでローマ字変換するスクリプトが公開されているサイトがありましたので、参考にさせていただきました。

問題は『ヘボン式』になっていないこと

ただ、スクリプトでひらがなの部分をカタカナに変えて、
「はい、終わり!」
というわけにはいかなかったのです。

なぜかというと、参考サイトで紹介されているスクリプトはあくまで訓令式ローマ字に変換するものであって、ヘボン式ではないから。
社内ではアカウント名をヘボン式で表記するルールがあったため、そのままでは使えません。

ちなみに、ヘボン式とはパスポートなどにも使えるローマ字表記で、たとえば下記のような例外ルールがあります。

※その他、個別・例外ルール
①「 ん 」 ・・・ 「 N 」に変換。ただし、「 NB 」・「 NM 」・「 NP 」となる場合は「 N→M 」で表記。
  (例: かんだ→KANDA 、 なんば→NAMBA 、 へんみ→HEMMI 、 らんぽ→RAMPO)
②「 っ 」 ・・・ 後に続く子音を重ねる。ただし、「 CH 」が続く場合は「 TCH 」となる。
  (例: べっぷ→BEPPU 、 こっち→KOTCHI )
③長音について ・・・ 母音の重なりを1文字で表記します。「 おお = OH 」表現も可能なようですが、任意表記のようなので変換君では考慮していません。
  (例: とおる→TORU 、 おおにし→ONISHI )

ヘボン式変換君サイトより引用

他にも微妙に違う表記方法の文字があるので、そういったことは下記サイトを確認しながら編集することに決めました。

サンプルコード

自分が考えたコードではないので、詳しい説明は割愛しますが、、、

要は、まず変換したい文字列(string)を受け取って、splitで分割。該当するカタカナの組み合わせが見つかったら(3文字→2文字→1文字)、紐付けたローマ字で変換していく流れ。

基本的な流れは、すべて参考サイトからの引用になりますが、カタカナの組み合わせはヘボン式用に追加・修正しています。

GAS
/**
 * カタカナ(フリガナ)をヘボン式ローマ字(人名・地名向けの短縮表記)に変換する関数
 *
 * @param {string} input - カタカナ
 * @return {string} ローマ字
 */
function kana2romaji(input) {
  // 1文字ずつ配列化して走査します(バッファで2~3文字の合成も見る)
  const arrayedString = String(input).split("");

  // 最終的な出力
  let value = "";

  // -----------------------------
  // 3文字合成(triTable)
  // 例:「リュウ」→ "ryu" のように “○ュウ/○ョウ” を一度に処理
  // -----------------------------
  const triTable = {
    // ○ュウ 系(短縮)
    "キュウ": "kyu", "シュウ": "shu", "チュウ": "chu",
    "ニュウ": "nyu", "ヒュウ": "hyu", "ミュウ": "myu", "リュウ": "ryu",

    // ○ョウ 系(短縮)
    "キョウ": "kyo", "ショウ": "sho", "チョウ": "cho",
    "ニョウ": "nyo", "ヒョウ": "hyo", "ミョウ": "myo", "リョウ": "ryo",
    "ギョウ": "gyo", "ジョウ": "jo", "ヂョウ": "dyo",
    "ビョウ": "byo","ピョウ": "pyo",

    // ○ュウ(濁音など)
    "ギュウ": "gyu", "ジュウ": "ju", "ビュウ": "byu", "ヂュウ": "dyu", "ピュウ": "pyu",

    // ヴ系(3文字合成の保険)
    "ウ゛ァ": "va","ウ゛ィ": "vi","ウ゛ゥ": "vu","ウ゛ェ": "ve","ウ゛ォ": "vo"
  };

  // -----------------------------
  // 2文字合成(biTable)
  // 例:「トウ」→ "to", 「チェ」→ "che"
  // -----------------------------
  const biTable = {
    // 長音の短縮(人名表記に合わせて “ou” にせず短く)
    // 「○オ」
    "オオ": "o","コオ": "ko","ソオ": "so","トオ": "to","ノオ": "no",
    "ホオ": "ho","モオ": "mo","ヨオ": "yo","ロオ": "ro",
    // 「○ウ」
    "クウ": "ku","スウ": "su","ツウ": "tsu","ヌウ": "nu","フウ": "fu",
    "ムウ": "mu","ユウ": "yu","ルウ": "ru",
    "コウ": "ko","ソウ": "so","トウ": "to","ノウ": "no",
    "ホウ": "ho","モウ": "mo","ヨウ": "yo","ロウ": "ro",
    "ゴウ": "go","ドウ": "do","ボウ": "bo","ポウ": "po",

    // 外来音・拗音(自然な綴り)
    "ジェ": "je","チェ": "che","シェ": "she",
    "ティ": "ti","ディ": "di","デュ": "dyu","トゥ": "tu","ドゥ": "du",
    "ファ": "fa","フィ": "fi","フェ": "fe","フォ": "fo",
    "ウィ": "wi","ウェ": "we","ウォ": "wo",
    "ヴァ": "va","ヴィ": "vi","ヴ": "vu","ヴェ": "ve","ヴォ": "vo",

    // 通常の拗音
    "ギャ": "gya","ギュ": "gyu","ギョ": "gyo",
    "シャ": "sha","シュ": "shu","ショ": "sho",
    "キャ": "kya","キュ": "kyu","キョ": "kyo",
    "ジャ": "ja","ジュ": "ju","ジョ": "jo",
    "チャ": "cha","チュ": "chu","チョ": "cho",
    "ヂャ": "dya","ヂュ": "dyu","ヂョ": "dyo",
    "デャ": "dha","デョ": "dho",
    "ニャ": "nya","ニュ": "nyu","ニョ": "nyo",
    "ヒャ": "hya","ヒュ": "hyu","ヒョ": "hyo",
    "ビャ": "bya","ビュ": "byu","ビョ": "byo",
    "ピャ": "pya","ピュ": "pyu","ピョ": "pyo",
    "ミャ": "mya","ミュ": "myu","ミョ": "myo",
    "リャ": "rya","リュ": "ryu","リョ": "ryo",

    // 促音(ッ)=重子音
    "ッカ": "kka","ッキ": "kki","ック": "kku","ッケ": "kke","ッコ": "kko",
    "ッサ": "ssa","ッシ": "sshi","ッス": "ssu","ッセ": "sse","ッソ": "sso",
    "ッタ": "tta","ッチ": "tchi","ッツ": "ttu","ッテ": "tte","ット": "tto",
    "ッナ": "nna","ッニ": "nni","ッヌ": "nnu","ッネ": "nne","ッノ": "nno",
    "ッハ": "hha","ッヒ": "hhi","ッフ": "ffu","ッヘ": "hhe","ッホ": "hho",
    "ッマ": "mma","ッミ": "mmi","ッム": "mmu","ッメ": "mme","ッモ": "mmo",
    "ッヤ": "yya","ッユ": "yyu","ッヨ": "yyo",
    "ッラ": "rra","ッリ": "rri","ッル": "rru","ッレ": "rre","ッロ": "rro",
    "ッワ": "wwa",
    "ッガ": "gga","ッギ": "ggi","ッグ": "ggu","ッゲ": "gge","ッゴ": "ggo",
    "ッザ": "zza","ッジ": "jji","ッズ": "zzu","ッゼ": "zze","ッゾ": "zzo",
    "ッダ": "dda","ッヂ": "ddi","ッヅ": "ddu","ッデ": "dde","ッド": "ddo",
    "ッバ": "bba","ッビ": "bbi","ッブ": "bbu","ッベ": "bbe","ッボ": "bbo",
    "ッパ": "ppa","ッピ": "ppi","ップ": "ppu","ッペ": "ppe","ッポ": "ppo"
  };

  // -----------------------------
  // 単音(uniTable)
  // 例:「カ」→ "ka"、「ン」→ "n"、伸ばし棒「ー」は無視
  // -----------------------------
  const uniTable = {
    "ア":"a","イ":"i","ウ":"u","エ":"e","オ":"o",
    "カ":"ka","キ":"ki","ク":"ku","ケ":"ke","コ":"ko",
    "サ":"sa","シ":"shi","ス":"su","セ":"se","ソ":"so",
    "タ":"ta","チ":"chi","ツ":"tsu","テ":"te","ト":"to",
    "ナ":"na","ニ":"ni","ヌ":"nu","ネ":"ne","ノ":"no",
    "ハ":"ha","ヒ":"hi","フ":"fu","ヘ":"he","ホ":"ho",
    "マ":"ma","ミ":"mi","ム":"mu","メ":"me","モ":"mo",
    "ヤ":"ya","ユ":"yu","ヨ":"yo",
    "ラ":"ra","リ":"ri","ル":"ru","レ":"re","ロ":"ro",
    "ワ":"wa","ヲ":"wo","ン":"n",
    "ガ":"ga","ギ":"gi","グ":"gu","ゲ":"ge","ゴ":"go",
    "ザ":"za","ジ":"ji","ズ":"zu","ゼ":"ze","ゾ":"zo",
    "ダ":"da","ヂ":"ji","ヅ":"zu","デ":"de","ド":"do",
    "バ":"ba","ビ":"bi","ブ":"bu","ベ":"be","ボ":"bo",
    "パ":"pa","ピ":"pi","プ":"pu","ペ":"pe","ポ":"po",
    "ァ":"xa","ィ":"xi","ゥ":"xu","ェ":"xe","ォ":"xo",
    "ー":"" // 伸ばし棒は短縮表記では無視(例:ファミリー→famiri)
  };

  // 「母音(拗音含む)」判定に使うセット
  const isVowelKana = ch =>
    ["ア","イ","ウ","エ","オ","ァ","ィ","ゥ","ェ","ォ","ヤ","ユ","ヨ","ャ","ュ","ョ"].includes(ch);

  // 入力全体がいきなり 3 文字/2 文字の合成パターンに一致する場合の早期リターン
  if (triTable[input] !== undefined) return triTable[input];
  if (biTable[input] !== undefined) return biTable[input];

  // 先頭文字が 2 文字/3 文字合成の“候補になり得るか”の目印セット
  const biCheck = {};
  for (const key in biTable) biCheck[key[0]] = true;

  const triCheck = {};
  for (const key in triTable) {
    triCheck[key[0] + key[1]] = true; // tri の先頭 2 文字
    biCheck[key[0]] = true; // tri の先頭 1 文字も「保留対象」に含める
  }

  // ここから本体:1~3文字のバッファ `buf` を使って逐次変換
  let buf = "";
  for (let i = 0; i < arrayedString.length; i++) {
    const ch = arrayedString[i];
    buf += ch;

    if (buf.length === 3) {
      // 3文字バッファ
      if (triTable[buf] !== undefined) {
        value += triTable[buf];
      } else {
        // 3文字ではマッチしない → 先頭2文字 or 1文字ずつで処理
        const a = buf[0], b = buf[1], c = buf[2];
        if (biTable[a + b] !== undefined) {
          value += biTable[a + b];
        } else {
          value += (uniTable[a] !== undefined ? uniTable[a] : a);
          value += (uniTable[b] !== undefined ? uniTable[b] : b);
        }
        value += (uniTable[c] !== undefined ? uniTable[c] : c);
      }
      buf = "";

    } else if (buf.length === 2) {
      // 2文字バッファ
      if (triCheck[buf] !== undefined) {
        // 3文字合成の可能性があるので、もう1文字待つ
      } else if (biTable[buf] !== undefined) {
        // 2文字合成に一致 → ただし長音短縮の“先読み”例外を確認
        const next = arrayedString[i + 1];
        const yRowLongU = (buf === "ヤウ" || buf === "ユウ" || buf === "ヨウ"); // Yu 系は短縮優先
        if (buf[1] === "ウ" && next && isVowelKana(next) && !yRowLongU) {
          // 直後が母音 → 短縮しない(例:ノウ + エ)
          value += (uniTable[buf[0]] !== undefined ? uniTable[buf[0]] : buf[0]); // 1文字目だけ確定
          buf = buf[1]; // 「ウ」を次ループで通常処理
        } else {
          // 通常は短縮(例:トウ→to、ヨウ→yo)
          value += biTable[buf];
          buf = "";
        }
      } else {
        // 2文字では未定義 → 1文字目だけ確定し、2文字目が“実在する連結候補”なら保留
        const a = buf[0], b = buf[1];
        value += (uniTable[a] !== undefined ? uniTable[a] : a);

        const next = arrayedString[i + 1];
        const pair = next ? (b + next) : null;
        const hasBiWithNext = pair ? (biTable[pair] !== undefined) : false; // 例:ド + ウ →「ドウ」
        const hasTriWithNext = pair ? (triCheck[pair] !== undefined) : false; // 例:キ + ョ →「キョ」(tri先頭2字)

        if (hasBiWithNext || hasTriWithNext) {
          buf = b; // 次の周回で 2文字/3文字合成に挑戦
        } else {
          value += (uniTable[b] !== undefined ? uniTable[b] : b);
          buf = "";
        }
      }

    } else if (i === arrayedString.length - 1) {
      // 残り1文字で入力が終わるときは確定
      value += (uniTable[ch] !== undefined ? uniTable[ch] : ch);
      buf = "";

    } else if (biCheck[buf] !== undefined) {
      // 2文字/3文字合成に発展する可能性があるので一旦キープ
      // 何もしない(次の文字を待つ)

    } else {
      // 合成の見込みがない単独文字はその場で確定
      value += (uniTable[ch] !== undefined ? uniTable[ch] : ch);
      buf = "";
    }
  }

  // ループ終了後に、バッファに取り残しがあれば出力(末尾落ち防止)
  if (buf.length === 1) {
    value += (uniTable[buf] !== undefined ? uniTable[buf] : buf);
    buf = "";
  } else if (buf.length === 2) {
    if (biTable[buf] !== undefined) {
      value += biTable[buf];
    } else {
      const a = buf[0], b = buf[1];
      value += (uniTable[a] !== undefined ? uniTable[a] : a);
      value += (uniTable[b] !== undefined ? uniTable[b] : b);
    }
    buf = "";
  }

  return value;
}

(2022/09/27追記)
テストサンプルをいくつか試していましたら、子音が続く場合(例:テッタ)に正常に動作しないことが判明しました。
修正内容は、下記2箇所です。

・uniTableからャ・ュ・ョ・ッを除外
・buf.length == 2の場合に子音が続くケースを追加条件分岐


(2023/05/31追記)
・文字数の判定で最後が3文字の場合に、bufに値が残ってしまう不具合を解消


(2024/01/25追記)
・クドウの場合、kudouになってしまう不具合を解消


(2024/06/03追記)
・スバラシイの場合、suundefinedrashiiになってしまう不具合を解消
⇒「ば」がunderfinedになっていたので修正


(2024/11/14追記)
・最後の一文字がローマ字変換されない不具合を修正


(2025/03/05追記)
・半角スペースおよび全角スペースがあるとunderfinedになる現象を改修


(2025/09/03改修)
・一部正しく変換できないフリガナ(例. イノウエ)があったため改修し、コードの体裁を全体的に整理

改良版サンプルコード

※追記(2024年09月10日)※
2024年9月時点で質問してくださった方の内容を反映させるために、
引数がカタカナでもひらがなでもどちらの場合でも最終的にローマ字に変換する関数を作成しました。

追加した関数はこちらのconvertCharacters関数です。

GAS
 /**
 * カタカナ⇔ひらがなに変換する関数
 *
 * @param {string} input - カタカナ or ひらがな
 * @param {string} kind - 変換したい入力文字種(カタカナ:0、ひらがな:1)
 * @return {string} カタカナ→ひらがな or ひらがな→カタカナ
 */
function convertCharacters (input,kind) {
  if(input == null) return "";
  
  // ひらがな、カタカナの一覧を文字列として取得する
  // ※文字位置はそれぞれ対応している(例:あ(2番目)⇔ア(2番目))
  const hiragana = "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろわをんゎゐゑゕゖ";
  const katakana = "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワヲンヮヰヱヵヶ";

  // デフォルト:変換前(ひらがな)、変換後(カタカナ)
  let before = hiragana;
  let after = katakana;
  const charArr = input.split('');
  // 入力文字にひらがなが含まれておらず、かつ、入力文字がひらがなの場合
  // 変換前をカタカナ、変換後をひらがなとする
  if(hiragana.indexOf(charArr[0]) === -1 && kind === 1){
    before = katakana;
    after = hiragana;
  // 入力文字にひらがなが含まれておらず、かつ、入力文字がカタカナ
  // または、入力文字にひらがなが含まれておらず、かつ、入力文字がひらがなの場合
  // 特に変換する必要はないため入力文字を返す
  }else if(hiragana.indexOf(charArr[0]) === -1 && kind === 0
         || hiragana.indexOf(charArr[0]) !== -1 && kind === 1){
    return input;
  }
  console.log(input);

  // 入力した文字を一文字ずつ取り出して変換する
  const afterText = charArr.map(function(char) {
                      var index = before.indexOf(char);
                      return index >= 0 ? after[index] : char;
                    }).join('');
  console.log(afterText);

  return afterText;
}

そして、完成形が下記になります。
カタカナ・ひらがな、どちらのバージョンでも対応したい場合には下記コードを活用してください。

GAS
/**
 * カタカナ(フリガナ)をヘボン式ローマ字(人名・地名向けの短縮表記)に変換する関数
 *
 * @param {string} input - カタカナ
 * @return {string} ローマ字
 */
function kana2romaji(input) {
  // 1文字ずつ配列化して走査します(バッファで2~3文字の合成も見る)
  const arrayedString = String(input).split("");

  // 最終的な出力
  let value = "";

  // -----------------------------
  // 3文字合成(triTable)
  // 例:「リュウ」→ "ryu" のように “○ュウ/○ョウ” を一度に処理
  // -----------------------------
  const triTable = {
    // ○ュウ 系(短縮)
    "キュウ": "kyu", "シュウ": "shu", "チュウ": "chu",
    "ニュウ": "nyu", "ヒュウ": "hyu", "ミュウ": "myu", "リュウ": "ryu",

    // ○ョウ 系(短縮)
    "キョウ": "kyo", "ショウ": "sho", "チョウ": "cho",
    "ニョウ": "nyo", "ヒョウ": "hyo", "ミョウ": "myo", "リョウ": "ryo",
    "ギョウ": "gyo", "ジョウ": "jo", "ヂョウ": "dyo",
    "ビョウ": "byo","ピョウ": "pyo",

    // ○ュウ(濁音など)
    "ギュウ": "gyu", "ジュウ": "ju", "ビュウ": "byu", "ヂュウ": "dyu", "ピュウ": "pyu",

    // ヴ系(3文字合成の保険)
    "ウ゛ァ": "va","ウ゛ィ": "vi","ウ゛ゥ": "vu","ウ゛ェ": "ve","ウ゛ォ": "vo"
  };

  // -----------------------------
  // 2文字合成(biTable)
  // 例:「トウ」→ "to", 「チェ」→ "che"
  // -----------------------------
  const biTable = {
    // 長音の短縮(人名表記に合わせて “ou” にせず短く)
    // 「○オ」
    "オオ": "o","コオ": "ko","ソオ": "so","トオ": "to","ノオ": "no",
    "ホオ": "ho","モオ": "mo","ヨオ": "yo","ロオ": "ro",
    // 「○ウ」
    "クウ": "ku","スウ": "su","ツウ": "tsu","ヌウ": "nu","フウ": "fu",
    "ムウ": "mu","ユウ": "yu","ルウ": "ru",
    "コウ": "ko","ソウ": "so","トウ": "to","ノウ": "no",
    "ホウ": "ho","モウ": "mo","ヨウ": "yo","ロウ": "ro",
    "ゴウ": "go","ドウ": "do","ボウ": "bo","ポウ": "po",

    // 外来音・拗音(自然な綴り)
    "ジェ": "je","チェ": "che","シェ": "she",
    "ティ": "ti","ディ": "di","デュ": "dyu","トゥ": "tu","ドゥ": "du",
    "ファ": "fa","フィ": "fi","フェ": "fe","フォ": "fo",
    "ウィ": "wi","ウェ": "we","ウォ": "wo",
    "ヴァ": "va","ヴィ": "vi","ヴ": "vu","ヴェ": "ve","ヴォ": "vo",

    // 通常の拗音
    "ギャ": "gya","ギュ": "gyu","ギョ": "gyo",
    "シャ": "sha","シュ": "shu","ショ": "sho",
    "キャ": "kya","キュ": "kyu","キョ": "kyo",
    "ジャ": "ja","ジュ": "ju","ジョ": "jo",
    "チャ": "cha","チュ": "chu","チョ": "cho",
    "ヂャ": "dya","ヂュ": "dyu","ヂョ": "dyo",
    "デャ": "dha","デョ": "dho",
    "ニャ": "nya","ニュ": "nyu","ニョ": "nyo",
    "ヒャ": "hya","ヒュ": "hyu","ヒョ": "hyo",
    "ビャ": "bya","ビュ": "byu","ビョ": "byo",
    "ピャ": "pya","ピュ": "pyu","ピョ": "pyo",
    "ミャ": "mya","ミュ": "myu","ミョ": "myo",
    "リャ": "rya","リュ": "ryu","リョ": "ryo",

    // 促音(ッ)=重子音
    "ッカ": "kka","ッキ": "kki","ック": "kku","ッケ": "kke","ッコ": "kko",
    "ッサ": "ssa","ッシ": "sshi","ッス": "ssu","ッセ": "sse","ッソ": "sso",
    "ッタ": "tta","ッチ": "tchi","ッツ": "ttu","ッテ": "tte","ット": "tto",
    "ッナ": "nna","ッニ": "nni","ッヌ": "nnu","ッネ": "nne","ッノ": "nno",
    "ッハ": "hha","ッヒ": "hhi","ッフ": "ffu","ッヘ": "hhe","ッホ": "hho",
    "ッマ": "mma","ッミ": "mmi","ッム": "mmu","ッメ": "mme","ッモ": "mmo",
    "ッヤ": "yya","ッユ": "yyu","ッヨ": "yyo",
    "ッラ": "rra","ッリ": "rri","ッル": "rru","ッレ": "rre","ッロ": "rro",
    "ッワ": "wwa",
    "ッガ": "gga","ッギ": "ggi","ッグ": "ggu","ッゲ": "gge","ッゴ": "ggo",
    "ッザ": "zza","ッジ": "jji","ッズ": "zzu","ッゼ": "zze","ッゾ": "zzo",
    "ッダ": "dda","ッヂ": "ddi","ッヅ": "ddu","ッデ": "dde","ッド": "ddo",
    "ッバ": "bba","ッビ": "bbi","ッブ": "bbu","ッベ": "bbe","ッボ": "bbo",
    "ッパ": "ppa","ッピ": "ppi","ップ": "ppu","ッペ": "ppe","ッポ": "ppo"
  };

  // -----------------------------
  // 単音(uniTable)
  // 例:「カ」→ "ka"、「ン」→ "n"、伸ばし棒「ー」は無視
  // -----------------------------
  const uniTable = {
    "ア":"a","イ":"i","ウ":"u","エ":"e","オ":"o",
    "カ":"ka","キ":"ki","ク":"ku","ケ":"ke","コ":"ko",
    "サ":"sa","シ":"shi","ス":"su","セ":"se","ソ":"so",
    "タ":"ta","チ":"chi","ツ":"tsu","テ":"te","ト":"to",
    "ナ":"na","ニ":"ni","ヌ":"nu","ネ":"ne","ノ":"no",
    "ハ":"ha","ヒ":"hi","フ":"fu","ヘ":"he","ホ":"ho",
    "マ":"ma","ミ":"mi","ム":"mu","メ":"me","モ":"mo",
    "ヤ":"ya","ユ":"yu","ヨ":"yo",
    "ラ":"ra","リ":"ri","ル":"ru","レ":"re","ロ":"ro",
    "ワ":"wa","ヲ":"wo","ン":"n",
    "ガ":"ga","ギ":"gi","グ":"gu","ゲ":"ge","ゴ":"go",
    "ザ":"za","ジ":"ji","ズ":"zu","ゼ":"ze","ゾ":"zo",
    "ダ":"da","ヂ":"ji","ヅ":"zu","デ":"de","ド":"do",
    "バ":"ba","ビ":"bi","ブ":"bu","ベ":"be","ボ":"bo",
    "パ":"pa","ピ":"pi","プ":"pu","ペ":"pe","ポ":"po",
    "ァ":"xa","ィ":"xi","ゥ":"xu","ェ":"xe","ォ":"xo",
    "ー":"" // 伸ばし棒は短縮表記では無視(例:ファミリー→famiri)
  };

  // 「母音(拗音含む)」判定に使うセット
  const isVowelKana = ch =>
    ["ア","イ","ウ","エ","オ","ァ","ィ","ゥ","ェ","ォ","ヤ","ユ","ヨ","ャ","ュ","ョ"].includes(ch);

  // 入力全体がいきなり 3 文字/2 文字の合成パターンに一致する場合の早期リターン
  if (triTable[input] !== undefined) return triTable[input];
  if (biTable[input] !== undefined) return biTable[input];

  // 先頭文字が 2 文字/3 文字合成の“候補になり得るか”の目印セット
  const biCheck = {};
  for (const key in biTable) biCheck[key[0]] = true;

  const triCheck = {};
  for (const key in triTable) {
    triCheck[key[0] + key[1]] = true; // tri の先頭 2 文字
    biCheck[key[0]] = true; // tri の先頭 1 文字も「保留対象」に含める
  }

  // ここから本体:1~3文字のバッファ `buf` を使って逐次変換
  let buf = "";
  for (let i = 0; i < arrayedString.length; i++) {
    const ch = arrayedString[i];
    buf += ch;

    if (buf.length === 3) {
      // 3文字バッファ
      if (triTable[buf] !== undefined) {
        value += triTable[buf];
      } else {
        // 3文字ではマッチしない → 先頭2文字 or 1文字ずつで処理
        const a = buf[0], b = buf[1], c = buf[2];
        if (biTable[a + b] !== undefined) {
          value += biTable[a + b];
        } else {
          value += (uniTable[a] !== undefined ? uniTable[a] : a);
          value += (uniTable[b] !== undefined ? uniTable[b] : b);
        }
        value += (uniTable[c] !== undefined ? uniTable[c] : c);
      }
      buf = "";

    } else if (buf.length === 2) {
      // 2文字バッファ
      if (triCheck[buf] !== undefined) {
        // 3文字合成の可能性があるので、もう1文字待つ
      } else if (biTable[buf] !== undefined) {
        // 2文字合成に一致 → ただし長音短縮の“先読み”例外を確認
        const next = arrayedString[i + 1];
        const yRowLongU = (buf === "ヤウ" || buf === "ユウ" || buf === "ヨウ"); // Yu 系は短縮優先
        if (buf[1] === "ウ" && next && isVowelKana(next) && !yRowLongU) {
          // 直後が母音 → 短縮しない(例:ノウ + エ)
          value += (uniTable[buf[0]] !== undefined ? uniTable[buf[0]] : buf[0]); // 1文字目だけ確定
          buf = buf[1]; // 「ウ」を次ループで通常処理
        } else {
          // 通常は短縮(例:トウ→to、ヨウ→yo)
          value += biTable[buf];
          buf = "";
        }
      } else {
        // 2文字では未定義 → 1文字目だけ確定し、2文字目が“実在する連結候補”なら保留
        const a = buf[0], b = buf[1];
        value += (uniTable[a] !== undefined ? uniTable[a] : a);

        const next = arrayedString[i + 1];
        const pair = next ? (b + next) : null;
        const hasBiWithNext = pair ? (biTable[pair] !== undefined) : false; // 例:ド + ウ →「ドウ」
        const hasTriWithNext = pair ? (triCheck[pair] !== undefined) : false; // 例:キ + ョ →「キョ」(tri先頭2字)

        if (hasBiWithNext || hasTriWithNext) {
          buf = b; // 次の周回で 2文字/3文字合成に挑戦
        } else {
          value += (uniTable[b] !== undefined ? uniTable[b] : b);
          buf = "";
        }
      }

    } else if (i === arrayedString.length - 1) {
      // 残り1文字で入力が終わるときは確定
      value += (uniTable[ch] !== undefined ? uniTable[ch] : ch);
      buf = "";

    } else if (biCheck[buf] !== undefined) {
      // 2文字/3文字合成に発展する可能性があるので一旦キープ
      // 何もしない(次の文字を待つ)

    } else {
      // 合成の見込みがない単独文字はその場で確定
      value += (uniTable[ch] !== undefined ? uniTable[ch] : ch);
      buf = "";
    }
  }

  // ループ終了後に、バッファに取り残しがあれば出力(末尾落ち防止)
  if (buf.length === 1) {
    value += (uniTable[buf] !== undefined ? uniTable[buf] : buf);
    buf = "";
  } else if (buf.length === 2) {
    if (biTable[buf] !== undefined) {
      value += biTable[buf];
    } else {
      const a = buf[0], b = buf[1];
      value += (uniTable[a] !== undefined ? uniTable[a] : a);
      value += (uniTable[b] !== undefined ? uniTable[b] : b);
    }
    buf = "";
  }

  return value;
}


/**
 * カタカナ⇔ひらがなに変換する関数
 *
 * @param {string} input - カタカナ or ひらがな
 * @param {string} kind - 変換したい入力文字種(カタカナ:0、ひらがな:1)
 * @return {string} カタカナ→ひらがな or ひらがな→カタカナ
 */
function convertCharacters (input,kind) {
  if(input == null) return "";
  
  // ひらがな、カタカナの一覧を文字列として取得する
  // ※文字位置はそれぞれ対応している(例:あ(2番目)⇔ア(2番目))
  const hiragana = "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろわをんゎゐゑゕゖ";
  const katakana = "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワヲンヮヰヱヵヶ";

  // デフォルト:変換前(ひらがな)、変換後(カタカナ)
  let before = hiragana;
  let after = katakana;
  const charArr = input.split('');
  // 入力文字にひらがなが含まれておらず、かつ、入力文字がひらがなの場合
  // 変換前をカタカナ、変換後をひらがなとする
  if(hiragana.indexOf(charArr[0]) === -1 && kind === 1){
    before = katakana;
    after = hiragana;
  // 入力文字にひらがなが含まれておらず、かつ、入力文字がカタカナ
  // または、入力文字にひらがなが含まれておらず、かつ、入力文字がひらがなの場合
  // 特に変換する必要はないため入力文字を返す
  }else if(hiragana.indexOf(charArr[0]) === -1 && kind === 0
        || hiragana.indexOf(charArr[0]) !== -1 && kind === 1){
    return input;
  }
  console.log(input);

  // 入力した文字を一文字ずつ取り出して変換する
  const afterText = charArr.map(function(char) {
                      var index = before.indexOf(char);
                      return index >= 0 ? after[index] : char;
                    }).join('');
  console.log(afterText);

  return afterText;
}


(2024/09/10追記)
・引数がひらがなでもカタカナでもどちらの場合でも対応できるバージョンを作成


(2025/09/03改修)
・一部正しく変換できないフリガナ(例. イノウエ)があったため改修し、コードの体裁を全体的に整理

まとめ

まだコードが出来上がったばかりで十分検証ができていませんので、もし不備がありましたらご指摘いただけますと幸いです!


最後まで読んでいただき、ありがとうございました!

19 COMMENTS

ぽんすけ

「くどう」が「kudou」になってしまっております。
調整いただけると助かります。

返信する
みそに

スバラシイ→suundefinedrashiiとなってしまいます。
調整していただけると幸いです。

返信する
hiro

初めまして

拡張機能からスクリプトをコピーして保存。
その後、実行してみるとエラーで、
TypeError: Cannot read properties of undefined (reading ‘split’)
と出てしまいます。
これは何が原因なのでしょうか。

返信する
おかちゃんせんせい

hiro様
はじめまして。
おそらくkana2romaji関数をそのまま実行されたのではないでしょうか。
kana2romajiには引数stringに文字列を渡してあげる必要があります。

hiro様がカナからローマ字に変換したい文字列をkana2romajiに渡してみていただければと思います。

下記が活用例になります。
function main(){
const kana = “ヤマダタロウ”;
const romaji = kana2romaji(kana);
console.log(romaji);
}

返信する
hiro

早々のお返事ありがとうございます。
全くの素人なのですが、スプレッドシート内でセル内のひらがなをローマ字に変換する関数ができないかなと模索しております。

セル内に関数で特定のセルのひらがなをローマ字にすることはできないのでしょうか?

返信する
おかちゃんせんせい
  • セル内に関数で特定のセルのひらがなをローマ字にすることはできないのでしょうか?
  • そちらの内容と、本日更新した記事の内容を基にすれば実現可能です。
    https://hajimarinomachi.com/gas-spreedsheet-function/

    また、ご質問いただいた内容を反映させるため、
    先ほどひらがなでもカタカナでも対応できるバージョンを公開しました。

    合わせてご確認ください。

    返信する
    hiro

    早々のお返事ありがとうございます。
    全くの素人なのですが、スプレッドシート内でセル内のひらがなをローマ字に変換する関数ができないかなと模索しております。

    セル内に関数で特定のセルのひらがなをローマ字にすることはできないのでしょうか?

    返信する
    n

    お世話になります。
    以下のキーワードがうまく表示されず困ってしまっております。ご相談可能でございますでしょうか。

    ヤハギ = yaha(gi がでない)
    オオハシ = oha(shi がでない)
    カヨ = ka(yo がでない)

    返信する
    おかちゃんせんせい

    お世話になります。

    不具合のご指摘ありがとございます。
    ご指摘いただいた内容について、修正いたしました。

    現在のスクリプトで再度検証をよろしくお願いいたします。

    返信する
    n

    反映を確認できました、、!
    早急にご対応いただきましてありがとうございました。

    返信する
    たかただ

    氏名の変換に利用させてもらっています。
    「スペース」があると「undefined」になるものがあります。
    イシバシ マサヤishibashiundefinedmasaya
    「スペース」を削除すると正常です。
    イシバシマサヤishibashimasaya
    「スペース」ありでも正常に変換されるものもあります。
    コンドウ ヨシミkondo yoshimi

    返信する
    おかちゃんせんせい

    たかただ様
    コメントいただきありがとうございます。

    スクリプトはスペースを考慮していなかったため、スペースがあるとunderfinedになってしまっていました。

    そこで、半角スペースまたは全角スペースがある場合には、スペースを保持して変換するように改修いたしました。
    改修版を公開しておりますので、ご確認ください。

    返信する
    たかただ

    おかちゃんせんせい様
    早々に改修いただき、ありがとうございました。
    完璧です。

    返信する
    たかただ

    以下のように表示されないところがあります
    キクチ ハルカ → kiku haruka
    ナンブ マナト → nambu manato
    イシバシ マサヤ → ishiba masaya
    ナガオ シズル → naga shizuru
    ヒラオ カンタ → hira kanta

    返信する

    コメントを残す

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    CAPTCHA