src/language/Japanese.js
/**
* The script is part of Mojix.
*
* AUTHOR:
* natade (http://twitter.com/natadea)
*
* LICENSE:
* The MIT license https://opensource.org/licenses/MIT
*/
import Unicode from "../encode/Unicode.js";
/**
* 日本語を扱うクラス
* @ignore
*/
export default class Japanese {
/**
* カタカナをひらがなに変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHiragana(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) - 0x0060);
};
return text.replace(/[\u30A1-\u30F6]/g, func);
}
/**
* ひらがなをカタカナに変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toKatakana(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) + 0x0060);
};
return text.replace(/[\u3041-\u3096]/g, func);
}
/**
* スペースを半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidthSpace(text) {
// prettier-ignore
return text.replace(/\u3000/g, String.fromCharCode(0x0020));
}
/**
* スペースを全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidthSpace(text) {
// prettier-ignore
return text.replace(/\u0020/g, String.fromCharCode(0x3000));
}
/**
* 英数記号を半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidthAsciiCode(text) {
let out = text;
out = out.replace(/\u3000/g, "\u0020"); //全角スペース
out = out.replace(/[\u2018-\u201B]/g, "\u0027"); //シングルクォーテーション
out = out.replace(/[\u201C-\u201F]/g, "\u0022"); //ダブルクォーテーション
/**
* @param {string} ch
*/
const func = function (ch) {
const code = ch.charCodeAt(0);
// prettier-ignore
return String.fromCharCode(code - 0xFEE0);
};
return out.replace(/[\uFF01-\uFF5E]/g, func);
}
/**
* 英数記号を全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidthAsciiCode(text) {
let out = text;
out = out.replace(/\u0020/g, "\u3000"); //全角スペース
out = out.replace(/\u0022/g, "\u201D"); //ダブルクォーテーション
out = out.replace(/\u0027/g, "\u2019"); //アポストロフィー
/**
* @param {string} ch
*/
const func = function (ch) {
const code = ch.charCodeAt(0);
// prettier-ignore
return String.fromCharCode(code + 0xFEE0);
};
return out.replace(/[\u0020-\u007E]/g, func);
}
/**
* アルファベットを半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidthAlphabet(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) - 0xFEE0);
};
return text.replace(/[\uFF21-\uFF3A\uFF41-\uFF5A]/g, func);
}
/**
* アルファベットを全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidthAlphabet(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) + 0xFEE0);
};
return text.replace(/[A-Za-z]/g, func);
}
/**
* 数値を半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidthNumber(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) - 0xFEE0);
};
return text.replace(/[\uFF10-\uFF19]/g, func);
}
/**
* 数値を全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidthNumber(text) {
/**
* @param {string} ch
*/
const func = function (ch) {
// prettier-ignore
return String.fromCharCode(ch.charCodeAt(0) + 0xFEE0);
};
return text.replace(/[0-9]/g, func);
}
/**
* カタカナを半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidthKana(text) {
/**
* @type {Object<number, string>}
*/
// prettier-ignore
const map = {
0x3001: "\uFF64", // 、
0x3002: "\uFF61", // 。 。
0x300C: "\uFF62", // 「 「
0x300D: "\uFF63", // 」 」
0x309B: "\uFF9E", // ゛ ゙
0x309C: "\uFF9F", // ゜ ゚
0x30A1: "\uFF67", // ァ ァ
0x30A2: "\uFF71", // ア ア
0x30A3: "\uFF68", // ィ ィ
0x30A4: "\uFF72", // イ イ
0x30A5: "\uFF69", // ゥ ゥ
0x30A6: "\uFF73", // ウ ウ
0x30A7: "\uFF6A", // ェ ェ
0x30A8: "\uFF74", // エ エ
0x30A9: "\uFF6B", // ォ ォ
0x30AA: "\uFF75", // オ オ
0x30AB: "\uFF76", // カ カ
0x30AC: "\uFF76\uFF9E", // ガ ガ
0x30AD: "\uFF77", // キ キ
0x30AE: "\uFF77\uFF9E", // ギ ギ
0x30AF: "\uFF78", // ク ク
0x30B0: "\uFF78\uFF9E", // グ グ
0x30B1: "\uFF79", // ケ ケ
0x30B2: "\uFF79\uFF9E", // ゲ ゲ
0x30B3: "\uFF7A", // コ コ
0x30B4: "\uFF7A\uFF9E", // ゴ ゴ
0x30B5: "\uFF7B", // サ サ
0x30B6: "\uFF7B\uFF9E", // ザ ザ
0x30B7: "\uFF7C", // シ シ
0x30B8: "\uFF7C\uFF9E", // ジ ジ
0x30B9: "\uFF7D", // ス ス
0x30BA: "\uFF7D\uFF9E", // ズ ズ
0x30BB: "\uFF7E", // セ セ
0x30BC: "\uFF7E\uFF9E", // ゼ ゼ
0x30BD: "\uFF7F", // ソ ソ
0x30BE: "\uFF7F\uFF9E", // ゾ ゾ
0x30BF: "\uFF80", // タ タ
0x30C0: "\uFF80\uFF9E", // ダ ダ
0x30C1: "\uFF81", // チ チ
0x30C2: "\uFF81\uFF9E", // ヂ ヂ
0x30C3: "\uFF6F", // ッ ッ
0x30C4: "\uFF82", // ツ ツ
0x30C5: "\uFF82\uFF9E", // ヅ ヅ
0x30C6: "\uFF83", // テ テ
0x30C7: "\uFF83\uFF9E", // デ デ
0x30C8: "\uFF84", // ト ト
0x30C9: "\uFF84\uFF9E", // ド ド
0x30CA: "\uFF85", // ナ ナ
0x30CB: "\uFF86", // ニ ニ
0x30CC: "\uFF87", // ヌ ヌ
0x30CD: "\uFF88", // ネ ネ
0x30CE: "\uFF89", // ノ ノ
0x30CF: "\uFF8A", // ハ ハ
0x30D0: "\uFF8A\uFF9E", // バ バ
0x30D1: "\uFF8A\uFF9F", // パ パ
0x30D2: "\uFF8B", // ヒ ヒ
0x30D3: "\uFF8B\uFF9E", // ビ ビ
0x30D4: "\uFF8B\uFF9F", // ピ ピ
0x30D5: "\uFF8C", // フ フ
0x30D6: "\uFF8C\uFF9E", // ブ ブ
0x30D7: "\uFF8C\uFF9F", // プ プ
0x30D8: "\uFF8D", // ヘ ヘ
0x30D9: "\uFF8D\uFF9E", // ベ ベ
0x30DA: "\uFF8D\uFF9F", // ペ ペ
0x30DB: "\uFF8E", // ホ ホ
0x30DC: "\uFF8E\uFF9E", // ボ ボ
0x30DD: "\uFF8E\uFF9F", // ポ ポ
0x30DE: "\uFF8F", // マ マ
0x30DF: "\uFF90", // ミ ミ
0x30E0: "\uFF91", // ム ム
0x30E1: "\uFF92", // メ メ
0x30E2: "\uFF93", // モ モ
0x30E3: "\uFF6C", // ャ ャ
0x30E4: "\uFF94", // ヤ ヤ
0x30E5: "\uFF6D", // ュ ュ
0x30E6: "\uFF95", // ユ ユ
0x30E7: "\uFF6E", // ョ ョ
0x30E8: "\uFF96", // ヨ ヨ
0x30E9: "\uFF97", // ラ ラ
0x30EA: "\uFF98", // リ リ
0x30EB: "\uFF99", // ル ル
0x30EC: "\uFF9A", // レ レ
0x30ED: "\uFF9B", // ロ ロ
0x30EE: "\uFF9C", // ヮ ワ
0x30EF: "\uFF9C", // ワ ワ
0x30F0: "\uFF72", // ヰ イ
0x30F1: "\uFF74", // ヱ エ
0x30F2: "\uFF66", // ヲ ヲ
0x30F3: "\uFF9D", // ン ン
0x30F4: "\uFF73\uFF9E", // ヴ ヴ
0x30F5: "\uFF76", // ヵ カ
0x30F6: "\uFF79", // ヶ ケ
0x30F7: "\uFF9C\uFF9E", // ヷ ヷ
0x30F8: "\uFF72\uFF9E", // ヸ イ゙
0x30F9: "\uFF74\uFF9E", // ヹ エ゙
0x30FA: "\uFF66\uFF9E", // ヺ ヺ
0x30FB: "\uFF65", // ・ ・
0x30FC: "\uFF70" // ー ー
};
/**
* @param {string} ch
*/
const func = function (ch) {
if (ch.length === 1) {
return map[ch.charCodeAt(0)];
} else {
return map[ch.charCodeAt(0)] + map[ch.charCodeAt(1)];
}
};
return text.replace(/[\u3001\u3002\u300C\u300D\u309B\u309C\u30A1-\u30FC][\u309B\u309C]?/g, func);
}
/**
* カタカナを全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidthKana(text) {
/**
* @type {Record<number, number>}
*/
// prettier-ignore
const map = {
0xFF61: 0x3002, // 。 。
0xFF62: 0x300C, // 「 「
0xFF63: 0x300D, // 」 」
0xFF64: 0x3001, // 、
0xFF65: 0x30FB, // ・ ・
0xFF66: 0x30F2, // ヲ ヲ
0xFF67: 0x30A1, // ァ ァ
0xFF68: 0x30A3, // ィ ィ
0xFF69: 0x30A5, // ゥ ゥ
0xFF6A: 0x30A7, // ェ ェ
0xFF6B: 0x30A9, // ォ ォ
0xFF6C: 0x30E3, // ャ ャ
0xFF6D: 0x30E5, // ュ ュ
0xFF6E: 0x30E7, // ョ ョ
0xFF6F: 0x30C3, // ッ ッ
0xFF70: 0x30FC, // ー ー
0xFF71: 0x30A2, // ア ア
0xFF72: 0x30A4, // イ イ
0xFF73: 0x30A6, // ウ ウ
0xFF74: 0x30A8, // エ エ
0xFF75: 0x30AA, // オ オ
0xFF76: 0x30AB, // カ カ
0xFF77: 0x30AD, // キ キ
0xFF78: 0x30AF, // ク ク
0xFF79: 0x30B1, // ケ ケ
0xFF7A: 0x30B3, // コ コ
0xFF7B: 0x30B5, // サ サ
0xFF7C: 0x30B7, // シ シ
0xFF7D: 0x30B9, // ス ス
0xFF7E: 0x30BB, // セ セ
0xFF7F: 0x30BD, // ソ ソ
0xFF80: 0x30BF, // タ タ
0xFF81: 0x30C1, // チ チ
0xFF82: 0x30C4, // ツ ツ
0xFF83: 0x30C6, // テ テ
0xFF84: 0x30C8, // ト ト
0xFF85: 0x30CA, // ナ ナ
0xFF86: 0x30CB, // ニ ニ
0xFF87: 0x30CC, // ヌ ヌ
0xFF88: 0x30CD, // ネ ネ
0xFF89: 0x30CE, // ノ ノ
0xFF8A: 0x30CF, // ハ ハ
0xFF8B: 0x30D2, // ヒ ヒ
0xFF8C: 0x30D5, // フ フ
0xFF8D: 0x30D8, // ヘ ヘ
0xFF8E: 0x30DB, // ホ ホ
0xFF8F: 0x30DE, // マ マ
0xFF90: 0x30DF, // ミ ミ
0xFF91: 0x30E0, // ム ム
0xFF92: 0x30E1, // メ メ
0xFF93: 0x30E2, // モ モ
0xFF94: 0x30E4, // ヤ ヤ
0xFF95: 0x30E6, // ユ ユ
0xFF96: 0x30E8, // ヨ ヨ
0xFF97: 0x30E9, // ラ ラ
0xFF98: 0x30EA, // リ リ
0xFF99: 0x30EB, // ル ル
0xFF9A: 0x30EC, // レ レ
0xFF9B: 0x30ED, // ロ ロ
0xFF9C: 0x30EF, // ワ ワ
0xFF9D: 0x30F3, // ン ン
0xFF9E: 0x309B, // ゛ ゙
0xFF9F: 0x309C // ゜ ゚
};
/**
* @param {string} str
*/
const func = function (str) {
if (str.length === 1) {
return String.fromCharCode(map[str.charCodeAt(0)]);
} else {
const next = str.charCodeAt(1);
const ch = str.charCodeAt(0);
if (next === 0xFF9E) {
// Shift-JISにない濁点(ヷ、ヸ、ヹ、ヺ)は意図的に無視
if (ch === 0xFF73) {
// ヴ
return String.fromCharCode(0x3094);
} else if ((0xFF76 <= ch && ch <= 0xFF84) || (0xFF8A <= ch && ch <= 0xFF8E)) {
// ガ-ド、バ-ボ
return String.fromCharCode(map[ch] + 1);
}
} else if (next === 0xFF9F) {
// 半濁点
if (0xFF8A <= ch && ch <= 0xFF8E) {
// パ-ポ
return String.fromCharCode(map[ch] + 2);
}
}
return String.fromCharCode(map[ch]) + String.fromCharCode(map[next]);
}
};
return text.replace(/[\uFF61-\uFF9F][\uFF9E\uFF9F]?/g, func);
}
/**
* 半角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHalfWidth(text) {
return Japanese.toHalfWidthKana(Japanese.toHalfWidthAsciiCode(text));
}
/**
* 全角に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toFullWidth(text) {
return Japanese.toFullWidthKana(Japanese.toFullWidthAsciiCode(text));
}
/**
* ローマ字からひらがなに変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toHiraganaFromRomaji(text) {
/**
* ローマ字から変換マップ
* .y[aiuoe] は除いている
* @type {Object<string, string>}
*/
// prettier-ignore
const map = {
a: "あ",
i: "い",
u: "う",
e: "え",
o: "お",
ka: "か",
ki: "き",
ku: "く",
ke: "け",
ko: "こ",
ga: "が",
gi: "ぎ",
gu: "ぐ",
ge: "げ",
go: "ご",
sa: "さ",
si: "し",
su: "す",
se: "せ",
so: "そ",
za: "ざ",
zi: "じ",
zu: "ず",
ze: "ぜ",
zo: "ぞ",
ta: "た",
ti: "ち",
tu: "つ",
te: "て",
to: "と",
da: "だ",
di: "ぢ",
du: "づ",
de: "で",
do: "ど",
na: "な",
ni: "に",
nu: "ぬ",
ne: "ね",
no: "の",
ha: "は",
hi: "ひ",
hu: "ふ",
he: "へ",
ho: "ほ",
ba: "ば",
bi: "び",
bu: "ぶ",
be: "べ",
bo: "ぼ",
pa: "ぱ",
pi: "ぴ",
pu: "ぷ",
pe: "ぺ",
po: "ぽ",
ma: "ま",
mi: "み",
mu: "む",
me: "め",
mo: "も",
ya: "や",
yi: "い",
yu: "ゆ",
ye: "いぇ",
yo: "よ",
ra: "ら",
ri: "り",
ru: "る",
re: "れ",
ro: "ろ",
wa: "わ",
wi: "うぃ",
wu: "う",
we: "うぇ",
wo: "を",
la: "ぁ",
li: "ぃ",
lu: "ぅ",
le: "ぇ",
lo: "ぉ",
lya: "ゃ",
lyi: "ぃ",
lyu: "ゅ",
lye: "ぇ",
lyo: "ょ",
ltu: "っ",
ltsu: "っ",
xa: "ぁ",
xi: "ぃ",
xu: "ぅ",
xe: "ぇ",
xo: "ぉ",
xya: "ゃ",
xyi: "ぃ",
xyu: "ゅ",
xye: "ぇ",
xyo: "ょ",
xtu: "っ",
xtsu: "っ",
// 環境依存をなくすために、SJISにあるカタカナにしています。
va: "ヴぁ",
vi: "ヴぃ",
vu: "ヴ",
ve: "ヴぇ",
vo: "ヴぉ",
qa: "くぁ",
qi: "くぃ",
qu: "く",
qe: "くぇ",
qo: "くぉ",
qwa: "くぁ",
qwi: "くぃ",
qwu: "くぅ",
qwe: "くぇ",
qwo: "くぉ",
gwa: "ぐぁ",
gwi: "ぐぃ",
gwu: "ぐぅ",
gwe: "ぐぇ",
gwo: "ぐぉ",
sha: "しゃ",
shi: "し",
shu: "しゅ",
she: "しぇ",
sho: "しょ",
swa: "すぁ",
swi: "すぃ",
swu: "すぅ",
swe: "すぇ",
swo: "すぉ",
cha: "ちゃ",
chi: "ち",
chu: "ちゅ",
che: "ちぇ",
cho: "ちょ",
tha: "ちゃ",
thi: "ち",
thu: "てゅ",
the: "てぇ",
tho: "てょ",
tsa: "つぁ",
tsi: "つぃ",
tsu: "つ",
tse: "つぇ",
tso: "つぉ",
twa: "とぁ",
twi: "とぃ",
twu: "とぅ",
twe: "とぇ",
two: "とぉ",
fa: "ふぁ",
fi: "ふぃ",
fu: "ふ",
fe: "ふぇ",
fo: "ふぉ",
fwa: "ふぁ",
fwi: "ふぃ",
fwu: "ふぅ",
fwe: "ふぇ",
fwo: "ふぉ",
ja: "じゃ",
ji: "じ",
ju: "じゅ",
je: "じぇ",
jo: "じょ",
n: "ん",
nn: "ん",
"-": "ー",
"?": "?",
"!": "!",
",": "、",
".": "。"
};
/**
* ya, yi, yu, ye, yo
* @type {Object<string, string>}
*/
// prettier-ignore
const y_komoji_map = {
a: "ゃ",
i: "ぃ",
u: "ゅ",
e: "ぇ",
o: "ょ"
};
/**
* @param {string} str
*/
const func = function (str) {
const output = [];
let y_komoji = null;
let romaji = str.toLowerCase();
if (romaji.length > 2) {
// 同じ文字の繰り返しなら「っ」に変更
if (romaji.charCodeAt(0) === romaji.charCodeAt(1)) {
// ただし繰り返し文字がnの場合は「ん」として扱う
if (romaji.slice(0, 1) === "n") {
output.push("ん");
romaji = romaji.slice(2);
} else {
output.push("っ");
romaji = romaji.slice(1);
}
}
}
if (romaji.length === 3) {
const char_1 = romaji.slice(0, 1);
const char_2 = romaji.slice(1, 2);
// 2文字目がyで始まる場合(ただし、lya, xya などを除く)は
// 小文字リストから選んで、最後に小文字をつける
// sya -> si につけかえて辞書から探す
if (char_2 === "y" && char_1 !== "l" && char_1 !== "x") {
y_komoji = y_komoji_map[romaji.slice(2)];
romaji = romaji.slice(0, 1) + "i";
}
}
const data = map[romaji];
if (!data) {
return str;
}
output.push(data);
if (y_komoji) {
output.push(y_komoji);
}
return output.join("");
};
/* eslint-disable max-len */
// 上から下への優先度で変換する。
// ([xl]?[kgsztdnhbpmyrwlxvqfj])(\1)?y?[aiuoe] ... yが入り込む可能性がある文字。前の文字を繰り返して「tta -> った」にも対応。
// [xl]?(gw|ch|cch|sh|ssh|ts|tts|th|tth)?[aiuoe] ... yを使用しない文字
// nn? ... ん
// [?!-] ... 記号
// prettier-ignore
return (text.replace(/([xl]?[kgsztdnhbpmyrwlxvqfj])(\1)?y?[aiuoe]|[xl]?([gqstf]w|ch|cch|sh|ssh|ts|tts|th|tth)?[aiuoe]|nn?|[?!-.,]/gi, func));
/* eslint-enable max-len */
}
/**
* ローマ字からカタカナに変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toKatakanaFromRomaji(text) {
return Japanese.toKatakana(Japanese.toHiraganaFromRomaji(text));
}
/**
* ひらがなからローマ字に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toRomajiFromHiragana(text) {
/**
* ひらがなからローマ字への変換マップ
* @type {Object<string, string>}
*/
// prettier-ignore
const map = {
あ: "a",
い: "i",
う: "u",
え: "e",
お: "o",
か: "ka",
き: "ki",
く: "ku",
け: "ke",
こ: "ko",
が: "ga",
ぎ: "gi",
ぐ: "gu",
げ: "ge",
ご: "go",
さ: "sa",
し: "shi",
す: "su",
せ: "se",
そ: "so",
ざ: "za",
じ: "ji",
ず: "zu",
ぜ: "ze",
ぞ: "zo",
た: "ta",
ち: "chi",
つ: "tsu",
て: "te",
と: "to",
だ: "da",
ぢ: "di",
づ: "du",
で: "de",
ど: "do",
な: "na",
に: "ni",
ぬ: "nu",
ね: "ne",
の: "no",
は: "ha",
ひ: "hi",
ふ: "fu",
へ: "he",
ほ: "ho",
ば: "ba",
び: "bi",
ぶ: "bu",
べ: "be",
ぼ: "bo",
ぱ: "pa",
ぴ: "pi",
ぷ: "pu",
ぺ: "pe",
ぽ: "po",
ま: "ma",
み: "mi",
む: "mu",
め: "me",
も: "mo",
や: "ya",
ゆ: "yu",
いぇ: "ye",
よ: "yo",
ら: "ra",
り: "ri",
る: "ru",
れ: "re",
ろ: "ro",
わ: "wa",
うぃ: "wi",
うぇ: "we",
うぉ: "wo",
を: "wo",
ゐ: "wi",
ゑ: "we",
ん: "n",
ぁ: "lya",
ぃ: "lyi",
ぅ: "lyu",
ぇ: "lye",
ぉ: "lyo",
ゃ: "lya",
ゅ: "lyu",
ょ: "lyo",
// 環境依存をなくすために、SJISにあるカタカナにしています。
ヴぁ: "va",
ヴぃ: "vi",
ヴ: "vu",
ヴぇ: "ve",
ヴぉ: "vo",
ゔぁ: "va",
ゔぃ: "vi",
ゔ: "vu",
ゔぇ: "ve",
ゔぉ: "vo",
きゃ: "kya",
きぃ: "kyi",
きゅ: "kyu",
きぇ: "kye",
きょ: "kyo",
ぎゃ: "gya",
ぎぃ: "gyi",
ぎゅ: "gyu",
ぎぇ: "gye",
ぎょ: "gyo",
くぁ: "qa",
くぃ: "qi",
くぅ: "qu",
くぇ: "qe",
くぉ: "qo",
ぐぁ: "gwa",
ぐぃ: "gwi",
ぐぅ: "gwu",
ぐぇ: "gwe",
ぐぉ: "gwo",
しゃ: "sha",
// "しぃ" : "shii" ,
しゅ: "shu",
しぇ: "she",
しょ: "sho",
じゃ: "ja",
// "じぃ" : "jii" ,
じゅ: "ju",
じぇ: "je",
じょ: "jo",
ちゃ: "cha",
// "ちぃ" : "chii"
ちゅ: "chu",
ちぇ: "che",
ちょ: "cho",
つぁ: "tsa",
つぃ: "tsi",
つぇ: "tse",
つぉ: "tso",
てぁ: "tha",
てぃ: "thi",
てゅ: "thu",
てぇ: "the",
てょ: "tho",
にゃ: "nya",
にぃ: "nyi",
にゅ: "nyu",
にぇ: "nye",
にょ: "nyo",
ひゃ: "hya",
ひぃ: "hyi",
ひゅ: "hyu",
ひぇ: "hye",
ひょ: "hyo",
びゃ: "bya",
びぃ: "byi",
びゅ: "byu",
びぇ: "bye",
びょ: "byo",
ぴゃ: "pya",
ぴぃ: "pyi",
ぴゅ: "pyu",
ぴぇ: "pye",
ぴょ: "pyo",
ふぁ: "fa",
ふぃ: "fi",
ふぇ: "fe",
ふぉ: "fo",
みゃ: "mya",
みぃ: "myi",
みゅ: "myu",
みぇ: "mye",
みょ: "myo",
りゃ: "rya",
りぃ: "ryi",
りゅ: "ryu",
りぇ: "rye",
りょ: "ryo",
ー: "-",
"?": "?",
"!": "!",
"、": ",",
"。": "."
};
/**
* @type {Object<string, string>}
*/
// prettier-ignore
const komoji_map = {
ぁ: "la",
ぃ: "li",
ぅ: "lu",
ぇ: "le",
ぉ: "lo",
ゃ: "lya",
ゅ: "lyu",
ょ: "lyo"
};
/**
* @param {string} str
*/
const func = function (str) {
let tgt = str;
let is_xtu = false;
// 1文字目に「っ」があるか
if (/^っ/.test(tgt)) {
is_xtu = true;
tgt = tgt.replace(/^っ*/, "");
}
// 変換
let trans = map[tgt];
// 変換に失敗した場合は
if (!trans) {
if (trans.length === 1) {
// 1文字なのでこれ以上変換不能
return str;
}
const char_1 = trans.slice(0, 1);
const char_2 = trans.slice(1, 2);
// 最後の文字が小文字である
if (!komoji_map[char_2]) {
// これ以上変換不能
return str;
}
tgt = char_1;
const last_text = komoji_map[char_2];
// 再度変換テスト
trans = map[tgt];
if (!trans) {
// これ以上変換不能
return str;
}
trans += last_text;
}
if (is_xtu) {
trans = trans.slice(0, 1) + trans;
}
return trans;
};
// [っ]*[あいうえおか-ぢつ-もやゆよら-ろわゐゑをんヴ][ぁぃぅぇぉゃゅょ]? ... 促音+子音母音
// [ぁぃぅぇぉゃゅょゎっ] ... 小文字のみ
// [?!-。、] ... 記号
// prettier-ignore
return (text.replace(/[っ]*[あいうえおか-ぢつ-もやゆよら-ろわゐゑをんヴゔ][ぁぃぅぇぉゃゅょ]?|[ぁぃぅぇぉゃゅょゎっ]|[?!-。、]/g, func));
}
/**
* カタカナからローマ字に変換
* @param {string} text - 変換したいテキスト
* @returns {string} 変換後のテキスト
*/
static toRomajiFromKatakana(text) {
return Japanese.toRomajiFromHiragana(Japanese.toHiragana(text));
}
/**
* 指定したコードポイントの横幅を推定して取得します
* - 0幅 ... グラフェムを構成する要素
* (結合文字, 異体字セレクタ, スキントーン修飾子,
* Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
* - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
* - 2幅 ... 上記以外
* @param {number} cp1 調査するコードポイント
* @param {number} [cp2] 調査するコードポイント
* @returns {number} 文字の横幅
*/
static getWidthFromCodePoint(cp1, cp2) {
if (cp2 !== undefined) {
if (Unicode.isRegionalIndicatorContinuation(cp1, cp2)) {
return 2;
}
}
if (Unicode.isGraphemeComponentFromCodePoint(cp1) || Unicode.isZeroWidthCharacterFromCodePoint(cp1)) {
return 0;
// prettier-ignore
} else if (cp1 < 0x80 || (0xFF61 <= cp1 && cp1 < 0xFFA0) || Unicode.isRegionalIndicatorFromCodePoint(cp1)) {
return 1;
} else {
return 2;
}
}
/**
* 指定したテキストの横幅を半角/全角でカウント
* - 0幅 ... グラフェムを構成する要素
* (結合文字, 異体字セレクタ, スキントーン修飾子,
* Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
* - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
* - 2幅 ... 上記以外
* @param {string} text - カウントしたいテキスト
* @returns {number} 文字の横幅
*/
static getWidth(text) {
const utf32_array = Unicode.toUTF32Array(text);
let count = 0;
let isZWJ = false;
for (let i = 0; i < utf32_array.length; i++) {
const cp = utf32_array[i];
// 国旗 (Regional Indicator)
if (i < utf32_array.length - 1) {
const next = utf32_array[i + 1];
if (Unicode.isRegionalIndicatorContinuation(cp, next)) {
if (!isZWJ) {
count += Japanese.getWidthFromCodePoint(cp, next);
}
i++;
isZWJ = false;
continue;
}
}
if (!isZWJ) {
count += Japanese.getWidthFromCodePoint(cp);
}
// prettier-ignore
isZWJ = cp === 0x200D;
}
return count;
}
/**
* 文字幅を考慮して文字列を文字の配列に変換する
* @param {string} text - 変換したいテキスト
* @returns {number[][]} UTF32(コードポイント)の配列が入った配列
*/
static toMojiArrayFromString(text) {
const utf32_array = Unicode.toUTF32Array(text);
/** @type {number[][]} */
const moji_array = [];
/** @type {number[]} */
let moji = [];
let isZWJ = false;
for (let i = 0; i < utf32_array.length; i++) {
const cp = utf32_array[i];
// --- 国旗 (Regional Indicator) は2つで1グラフェム ---
if (i < utf32_array.length - 1) {
const next = utf32_array[i + 1];
if (Unicode.isRegionalIndicatorContinuation(cp, next)) {
// 前のグラフェムを確定
if (moji.length > 0) {
moji_array.push(moji);
}
// RIペアで新しいグラフェムを作る
moji = [cp, next];
moji_array.push(moji);
moji = []; // 次のグラフェムに備える
i++; // 2つ目のRIを消費
isZWJ = false;
continue;
}
}
// --- 新しいグラフェム開始判定 ---
// 「ZWJ直後」または「グラフェム構成要素」は前に結合させる
const isComponent = Unicode.isGraphemeComponentFromCodePoint(cp);
if (!isZWJ && !isComponent) {
// ベース文字が来たので、前のグラフェムを確定して新しく開始
if (moji.length > 0) {
moji_array.push(moji);
}
moji = [];
}
moji.push(cp);
// 次ループ用:ZWJ は次の文字とグラフェムを結合するため、新しい境界を作らないフラグを立てる
isZWJ = (cp === 0x200D);
}
// 末尾が残っていれば追加
if (moji.length > 0) {
moji_array.push(moji);
}
return moji_array;
}
/**
* 結合した文字を考慮して文字の配列を文字列に変換する
* @param {number[][]} mojiarray - UTF32(コードポイント)の配列が入った配列
* @returns {string} UTF32(コードポイント)の配列が入った配列
*/
static toStringFromMojiArray(mojiarray) {
/**
* @type {number[]}
*/
const utf32 = [];
for (let i = 0; i < mojiarray.length; i++) {
for (let j = 0; j < mojiarray[i].length; j++) {
utf32.push(mojiarray[i][j]);
}
}
return Unicode.fromUTF32Array(utf32);
}
/**
* 指定したテキストの横幅を半角/全角で換算した場合の切り出し
* - 0幅 ... グラフェムを構成する要素
* (結合文字, 異体字セレクタ, スキントーン修飾子,
* Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
* - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
* - 2幅 ... 上記以外
* @param {string} text - 切り出したいテキスト
* @param {number} offset - 切り出し位置
* @param {number} size - 切り出す長さ
* @returns {string} 切り出したテキスト
* @ignore
*/
static cutTextForWidth(text, offset, size) {
const moji_array = Japanese.toMojiArrayFromString(text);
const SPACE = [0x20]; // ' '
/**
* @type {number[][]}
*/
const output = [];
let is_target = false;
let position = 0;
let cut_size = size;
if (offset < 0) {
cut_size += offset;
offset = 0;
}
if (cut_size <= 0) {
return "";
}
for (let i = 0; i < moji_array.length; i++) {
// 文字データ
const moji = moji_array[i];
// 1文字目の横幅を取得
const cp = moji[0];
// ASCII文字, 半角カタカナ, Regional Indicator(単体)
// prettier-ignore
const cp_size = cp < 0x80
|| (0xFF61 <= cp && cp < 0xFFA0)
|| (moji.length === 1 && Unicode.isRegionalIndicatorFromCodePoint(cp)) ? 1 : 2;
if (position >= offset) {
is_target = true;
if (cut_size >= cp_size) {
output.push(moji);
} else {
output.push(SPACE);
}
cut_size -= cp_size;
if (cut_size <= 0) {
break;
}
}
position += cp_size;
// 2バイト文字の途中をoffset指定していた場合になる。
if (position - 1 >= offset && !is_target) {
cut_size--;
output.push(SPACE);
}
}
return Japanese.toStringFromMojiArray(output);
}
}