LoginSignup
11
4

More than 1 year has passed since last update.

JavaScriptでローマ字をカタカナに変換する関数

Last updated at Posted at 2019-05-07

概要

入力を正規化する必要があったので、ローマ字からカタカナに変換する関数を作成しました。
IMEと似た感じで変換されます(表にないものはそのまま出ます)。

aiueoアイウエオ
hankachiハンカチ
tonari no kyaku ha yoku kakikuu kyakudaトナリ ノ キャク ハ ヨク カキクウ キャクダ
toukyou tokkyo kyokakyokuトウキョウ トッキョ キョカキョク
qawsedrftgyhujikolpクァwセdrftgyフジコlp
高輪Gateway高輪ガテワy

コード

TSで書いているので、JSで使う場合型情報を落としてください。
促音やへの対応は行っています。
抜けや不具合があったらご報告ください。

※ Google IMEで調べたところ、wが連続したときだけ他と違う挙動を示しましたが、面倒だったので他と同じようにただの促音になります。



interface ITree {
  [key: string]: ITree | string;
}

const tree: ITree = {
  a: '', i: '', u: '', e: '', o: '',
  k: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'キャ', i: 'キィ', u: 'キュ', e: 'キェ', o: 'キョ' },
  },
  s: {
    a: '', i: '', u: '', e: '', o: '',
    h: { a: 'シャ', i: '', u: 'シュ', e: 'シェ', o: 'ショ' },
    y: { a: 'シャ', i: 'シィ', u: 'シュ', e: 'シェ', o: 'ショ' },
  },
  t: {
    a: '', i: '', u: '', e: '', o: '',
    h: { a: 'テャ', i: 'ティ', u: 'テュ', e: 'テェ', o: 'テョ' },
    y: { a: 'チャ', i: 'チィ', u: 'チュ', e: 'チェ', o: 'チョ' },
    s: { a: 'ツァ', i: 'ツィ', u: '', e: 'ツェ', o: 'ツォ' },
  },
  c: {
    a: '', i: '', u: '', e: '', o: '',
    h: { a: 'チャ', i: '', u: 'チュ', e: 'チェ', o: 'チョ' },
    y: { a: 'チャ', i: 'チィ', u: 'チュ', e: 'チェ', o: 'チョ' },
  },
  q: {
    a: 'クァ', i: 'クィ', u: '', e: 'クェ', o: 'クォ',
  },
  n: {
    a: '', i: '', u: '', e: '', o: '', n: '',
    y: { a: 'ニャ', i: 'ニィ', u: 'ニュ', e: 'ニェ', o: 'ニョ' },
  },
  h: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ヒャ', i: 'ヒィ', u: 'ヒュ', e: 'ヒェ', o: 'ヒョ' },
  },
  f: {
    a: 'ファ', i: 'フィ', u: '', e: 'フェ', o: 'フォ',
    y: { a: 'フャ', u: 'フュ', o: 'フョ' },
  },
  m: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ミャ', i: 'ミィ', u: 'ミュ', e: 'ミェ', o: 'ミョ' },
  },
  y: { a: '', i: '', u: '', e: 'イェ', o: '' },
  r: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'リャ', i: 'リィ', u: 'リュ', e: 'リェ', o: 'リョ' },
  },
  w: { a: '', i: 'ウィ', u: '', e: 'ウェ', o: '' },
  g: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ギャ', i: 'ギィ', u: 'ギュ', e: 'ギェ', o: 'ギョ' },
  },
  z: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ジャ', i: 'ジィ', u: 'ジュ', e: 'ジェ', o: 'ジョ' },
  },
  j: {
    a: 'ジャ', i: '', u: 'ジュ', e: 'ジェ', o: 'ジョ',
    y: { a: 'ジャ', i: 'ジィ', u: 'ジュ', e: 'ジェ', o: 'ジョ' },
  },
  d: {
    a: '', i: '', u: '', e: '', o: '',
    h: { a: 'デャ', i: 'ディ', u: 'デュ', e: 'デェ', o: 'デョ' },
    y: { a: 'ヂャ', i: 'ヂィ', u: 'ヂュ', e: 'ヂェ', o: 'ヂョ' },
  },
  b: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ビャ', i: 'ビィ', u: 'ビュ', e: 'ビェ', o: 'ビョ' },
  },
  v: {
    a: 'ヴァ', i: 'ヴィ', u: '', e: 'ヴェ', o: 'ヴォ',
    y: { a: 'ヴャ', i: 'ヴィ', u: 'ヴュ', e: 'ヴェ', o: 'ヴョ' },
  },
  p: {
    a: '', i: '', u: '', e: '', o: '',
    y: { a: 'ピャ', i: 'ピィ', u: 'ピュ', e: 'ピェ', o: 'ピョ' },
  },
  x: {
    a: '', i: '', u: '', e: '', o: '',
    y: {
      a: '', i: '', u: '', e: '', o: '',
    },
    t: {
      u: '',
      s: {
        u: '',
      },
    },
  },
  l: {
    a: '', i: '', u: '', e: '', o: '',
    y: {
      a: '', i: '', u: '', e: '', o: '',
    },
    t: {
      u: '',
      s: {
        u: '',
      },
    },
  },
};

export function convertRomanToKana(original: string) {
  const str = original.replace(/[A-Za-z]/, s => String.fromCharCode(s.charCodeAt(0) - 65248)).toLowerCase(); // 全角→半角→小文字
  let result = '';
  let tmp = '';
  let index = 0;
  const len = str.length;
  let node = tree;
  const push = (char: string, toRoot = true) => {
    result += char;
    tmp = '';
    node = toRoot ? tree : node;
  };
  while (index < len) {
    const char = str.charAt(index);
    if (char.match(/[a-z]/)) { // 英数字以外は考慮しない
      if (char in node) {
        const next = node[char];
        if (typeof next === 'string') {
          push(next);
        } else {
          tmp += original.charAt(index);
          node = next;
        }
        index++;
        continue;
      }
      const prev = str.charAt(index - 1);
      if (prev && (prev === 'n' || prev === char)) { // 促音やnへの対応
        push(prev === 'n' ? '' : '', false);
      }
      if (node !== tree && char in tree) { // 今のノードがルート以外だった場合、仕切り直してチェックする
        push(tmp);
        continue;
      }
    }
    push(tmp + char);
    index++;
  }
  tmp = tmp.replace(/n$/, ''); // 末尾のnは変換する
  push(tmp);
  return result;
}

参考

変換表はこちらの記事を元に叩き台を作成しました。
https://qiita.com/le_panda_noir/items/5be2639f77905879432a
書かれている通り、表を作成するのは地味に面倒でした…(IMEのカナ変換に生まれて初めて感謝しました)。

全角→半角変換はこちらの記事を参考にしました。
https://qiita.com/yamikoo@github/items/5dbcc77b267a549bdbae

11
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4