Initial commit.

This commit is contained in:
sergiotarxz 2024-06-24 05:14:30 +02:00
parent 72769f2e7b
commit 586b1f85f7
12 changed files with 2675 additions and 0 deletions

26
Build.PL Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env perl
use Module::Build;
my $home = $ENV{HOME};
my $build = Module::Build->new(
module_name => 'JapaChar',
license => 'AGPLv3',
dist_author => 'Sergio Iglesias <contact@owlcode.tech>',
dist_abstract => 'Learn Japanese Characters.',
requires => {
'DBI' => 0,
'DBD::SQLite' => 0,
'Cairo::GObject' => 0,
'Glib::IO' => 0,
'Glib::Object::Introspection' => 0,
'JSON' => 0,
'Moo' => 0,
'YAML::PP' => 0,
'Path::Tiny' => 0,
'DBIx::Class' => 0,
'Pango' => 0,
'Crypt::URandom' => 0,
},
);
$build->create_build_script;

0
config.yml Normal file
View File

997
hiragana.json Normal file
View File

@ -0,0 +1,997 @@
[
{
"kana": "あ",
"roumaji": "a",
"type": "gojuuon"
},
{
"kana": "い",
"roumaji": "i",
"type": "gojuuon"
},
{
"kana": "う",
"roumaji": "u",
"type": "gojuuon"
},
{
"kana": "え",
"roumaji": "e",
"type": "gojuuon"
},
{
"kana": "お",
"roumaji": "o",
"type": "gojuuon"
},
{
"kana": "か",
"roumaji": "ka",
"type": "gojuuon"
},
{
"kana": "き",
"roumaji": "ki",
"type": "gojuuon"
},
{
"kana": "く",
"roumaji": "ku",
"type": "gojuuon"
},
{
"kana": "け",
"roumaji": "ke",
"type": "gojuuon"
},
{
"kana": "こ",
"roumaji": "ko",
"type": "gojuuon"
},
{
"kana": "さ",
"roumaji": "sa",
"type": "gojuuon"
},
{
"kana": "し",
"roumaji": "shi",
"type": "gojuuon"
},
{
"kana": "す",
"roumaji": "su",
"type": "gojuuon"
},
{
"kana": "せ",
"roumaji": "se",
"type": "gojuuon"
},
{
"kana": "そ",
"roumaji": "so",
"type": "gojuuon"
},
{
"kana": "た",
"roumaji": "ta",
"type": "gojuuon"
},
{
"kana": "ち",
"roumaji": "chi",
"type": "gojuuon"
},
{
"kana": "つ",
"roumaji": "tsu",
"type": "gojuuon"
},
{
"kana": "て",
"roumaji": "te",
"type": "gojuuon"
},
{
"kana": "と",
"roumaji": "to",
"type": "gojuuon"
},
{
"kana": "な",
"roumaji": "na",
"type": "gojuuon"
},
{
"kana": "に",
"roumaji": "ni",
"type": "gojuuon"
},
{
"kana": "ぬ",
"roumaji": "nu",
"type": "gojuuon"
},
{
"kana": "ね",
"roumaji": "ne",
"type": "gojuuon"
},
{
"kana": "の",
"roumaji": "no",
"type": "gojuuon"
},
{
"kana": "は",
"roumaji": "ha",
"type": "gojuuon"
},
{
"kana": "ひ",
"roumaji": "hi",
"type": "gojuuon"
},
{
"kana": "ふ",
"roumaji": "fu",
"type": "gojuuon"
},
{
"kana": "へ",
"roumaji": "he",
"type": "gojuuon"
},
{
"kana": "ほ",
"roumaji": "ho",
"type": "gojuuon"
},
{
"kana": "ま",
"roumaji": "ma",
"type": "gojuuon"
},
{
"kana": "み",
"roumaji": "mi",
"type": "gojuuon"
},
{
"kana": "む",
"roumaji": "mu",
"type": "gojuuon"
},
{
"kana": "め",
"roumaji": "me",
"type": "gojuuon"
},
{
"kana": "も",
"roumaji": "mo",
"type": "gojuuon"
},
{
"kana": "や",
"roumaji": "ya",
"type": "gojuuon"
},
{
"kana": "ゆ",
"roumaji": "yu",
"type": "gojuuon"
},
{
"kana": "よ",
"roumaji": "yo",
"type": "gojuuon"
},
{
"kana": "ら",
"roumaji": "ra",
"type": "gojuuon"
},
{
"kana": "り",
"roumaji": "ri",
"type": "gojuuon"
},
{
"kana": "る",
"roumaji": "ru",
"type": "gojuuon"
},
{
"kana": "れ",
"roumaji": "re",
"type": "gojuuon"
},
{
"kana": "ろ",
"roumaji": "ro",
"type": "gojuuon"
},
{
"kana": "わ",
"roumaji": "wa",
"type": "gojuuon"
},
{
"kana": "を",
"roumaji": "wo",
"type": "gojuuon"
},
{
"kana": "ん",
"roumaji": "n",
"type": "gojuuon"
},
{
"kana": "が",
"roumaji": "ga",
"type": "dakuon"
},
{
"kana": "ぎ",
"roumaji": "gi",
"type": "dakuon"
},
{
"kana": "ぐ",
"roumaji": "gu",
"type": "dakuon"
},
{
"kana": "げ",
"roumaji": "ge",
"type": "dakuon"
},
{
"kana": "ご",
"roumaji": "go",
"type": "dakuon"
},
{
"kana": "ざ",
"roumaji": "za",
"type": "dakuon"
},
{
"kana": "じ",
"roumaji": "ji",
"type": "dakuon"
},
{
"kana": "ず",
"roumaji": "zu",
"type": "dakuon"
},
{
"kana": "ぜ",
"roumaji": "ze",
"type": "dakuon"
},
{
"kana": "ぞ",
"roumaji": "zo",
"type": "dakuon"
},
{
"kana": "だ",
"roumaji": "da",
"type": "dakuon"
},
{
"kana": "ぢ",
"roumaji": "ji",
"type": "dakuon"
},
{
"kana": "づ",
"roumaji": "zu",
"type": "dakuon"
},
{
"kana": "で",
"roumaji": "de",
"type": "dakuon"
},
{
"kana": "ど",
"roumaji": "do",
"type": "dakuon"
},
{
"kana": "ば",
"roumaji": "ba",
"type": "dakuon"
},
{
"kana": "び",
"roumaji": "bi",
"type": "dakuon"
},
{
"kana": "ぶ",
"roumaji": "bu",
"type": "dakuon"
},
{
"kana": "べ",
"roumaji": "be",
"type": "dakuon"
},
{
"kana": "ぼ",
"roumaji": "bo",
"type": "dakuon"
},
{
"kana": "ぱ",
"roumaji": "pa",
"type": "handakuon"
},
{
"kana": "ぴ",
"roumaji": "pi",
"type": "handakuon"
},
{
"kana": "ぷ",
"roumaji": "pu",
"type": "handakuon"
},
{
"kana": "ぺ",
"roumaji": "pe",
"type": "handakuon"
},
{
"kana": "ぽ",
"roumaji": "po",
"type": "handakuon"
},
{
"kana": "っ",
"roumaji": "(pause)",
"type": "sokuon"
},
{
"kana": "きゃ",
"roumaji": "kya",
"type": "youon"
},
{
"kana": "きゅ",
"roumaji": "kyu",
"type": "youon"
},
{
"kana": "きょ",
"roumaji": "kyo",
"type": "youon"
},
{
"kana": "しゃ",
"roumaji": "sha",
"type": "youon"
},
{
"kana": "しゅ",
"roumaji": "shu",
"type": "youon"
},
{
"kana": "しょ",
"roumaji": "sho",
"type": "youon"
},
{
"kana": "ちゃ",
"roumaji": "cha",
"type": "youon"
},
{
"kana": "ちゅ",
"roumaji": "chu",
"type": "youon"
},
{
"kana": "ちょ",
"roumaji": "cho",
"type": "youon"
},
{
"kana": "にゃ",
"roumaji": "nya",
"type": "youon"
},
{
"kana": "にゅ",
"roumaji": "nyu",
"type": "youon"
},
{
"kana": "にょ",
"roumaji": "nyo",
"type": "youon"
},
{
"kana": "ひゃ",
"roumaji": "hya",
"type": "youon"
},
{
"kana": "ひゅ",
"roumaji": "hyu",
"type": "youon"
},
{
"kana": "ひょ",
"roumaji": "hyo",
"type": "youon"
},
{
"kana": "みゃ",
"roumaji": "mya",
"type": "youon"
},
{
"kana": "みゅ",
"roumaji": "myu",
"type": "youon"
},
{
"kana": "みょ",
"roumaji": "myo",
"type": "youon"
},
{
"kana": "りゃ",
"roumaji": "rya",
"type": "youon"
},
{
"kana": "りゅ",
"roumaji": "ryu",
"type": "youon"
},
{
"kana": "りょ",
"roumaji": "ryo",
"type": "youon"
},
{
"kana": "ぎゃ",
"roumaji": "gya",
"type": "youon"
},
{
"kana": "ぎゅ",
"roumaji": "gyu",
"type": "youon"
},
{
"kana": "ぎょ",
"roumaji": "gyo",
"type": "youon"
},
{
"kana": "じゃ",
"roumaji": "ja",
"type": "youon"
},
{
"kana": "じゅ",
"roumaji": "ju",
"type": "youon"
},
{
"kana": "じょ",
"roumaji": "jo",
"type": "youon"
},
{
"kana": "びゃ",
"roumaji": "bya",
"type": "youon"
},
{
"kana": "びゅ",
"roumaji": "byu",
"type": "youon"
},
{
"kana": "びょ",
"roumaji": "byo",
"type": "youon"
},
{
"kana": "ぴゃ",
"roumaji": "pya",
"type": "youon"
},
{
"kana": "ぴゅ",
"roumaji": "pyu",
"type": "youon"
},
{
"kana": "ぴょ",
"roumaji": "pyo",
"type": "youon"
},
{
"kana": "いぃ",
"roumaji": "yi",
"type": "extended"
},
{
"kana": "いぇ",
"roumaji": "ye",
"type": "extended"
},
{
"kana": "ゔぁ",
"roumaji": "va",
"type": "extended"
},
{
"kana": "ゔぃ",
"roumaji": "vi",
"type": "extended"
},
{
"kana": "ゔ",
"roumaji": "vu",
"type": "extended"
},
{
"kana": "ゔぇ",
"roumaji": "ve",
"type": "extended"
},
{
"kana": "ゔぉ",
"roumaji": "vo",
"type": "extended"
},
{
"kana": "ゔゃ",
"roumaji": "vya",
"type": "extended"
},
{
"kana": "ゔゅ",
"roumaji": "vyu",
"type": "extended"
},
{
"kana": "ゔょ",
"roumaji": "vyo",
"type": "extended"
},
{
"kana": "しぇ",
"roumaji": "she",
"type": "extended"
},
{
"kana": "じぇ",
"roumaji": "je",
"type": "extended"
},
{
"kana": "ちぇ",
"roumaji": "che",
"type": "extended"
},
{
"kana": "すぁ",
"roumaji": "swa",
"type": "extended"
},
{
"kana": "すぃ",
"roumaji": "swi",
"type": "extended"
},
{
"kana": "すぅ",
"roumaji": "swu",
"type": "extended"
},
{
"kana": "すぇ",
"roumaji": "swe",
"type": "extended"
},
{
"kana": "すぉ",
"roumaji": "swo",
"type": "extended"
},
{
"kana": "すゃ",
"roumaji": "sya",
"type": "extended"
},
{
"kana": "すゅ",
"roumaji": "syu",
"type": "extended"
},
{
"kana": "すょ",
"roumaji": "syo",
"type": "extended"
},
{
"kana": "せぃ",
"roumaji": "si",
"type": "extended"
},
{
"kana": "ずぁ",
"roumaji": "zwa",
"type": "extended"
},
{
"kana": "ずぃ",
"roumaji": "zwi",
"type": "extended"
},
{
"kana": "ずぅ",
"roumaji": "zwu",
"type": "extended"
},
{
"kana": "ずぇ",
"roumaji": "zwe",
"type": "extended"
},
{
"kana": "ずぉ",
"roumaji": "zwo",
"type": "extended"
},
{
"kana": "ずゃ",
"roumaji": "zya",
"type": "extended"
},
{
"kana": "ずゅ",
"roumaji": "zyu",
"type": "extended"
},
{
"kana": "ずょ",
"roumaji": "zyo",
"type": "extended"
},
{
"kana": "ゼぃ",
"roumaji": "zi",
"type": "extended"
},
{
"kana": "つぁ",
"roumaji": "tsa",
"type": "extended"
},
{
"kana": "つぃ",
"roumaji": "tsi",
"type": "extended"
},
{
"kana": "つぇ",
"roumaji": "tse",
"type": "extended"
},
{
"kana": "つぉ",
"roumaji": "tso",
"type": "extended"
},
{
"kana": "てぁ",
"roumaji": "tha",
"type": "extended"
},
{
"kana": "てぃ",
"roumaji": "ti",
"type": "extended"
},
{
"kana": "てぅ",
"roumaji": "thu",
"type": "extended"
},
{
"kana": "てぇ",
"roumaji": "tye",
"type": "extended"
},
{
"kana": "てぉ",
"roumaji": "tho",
"type": "extended"
},
{
"kana": "てゃ",
"roumaji": "tya",
"type": "extended"
},
{
"kana": "てゅ",
"roumaji": "tyu",
"type": "extended"
},
{
"kana": "てょ",
"roumaji": "tyo",
"type": "extended"
},
{
"kana": "でぁ",
"roumaji": "dha",
"type": "extended"
},
{
"kana": "でぃ",
"roumaji": "di",
"type": "extended"
},
{
"kana": "でぅ",
"roumaji": "dhu",
"type": "extended"
},
{
"kana": "でぇ",
"roumaji": "dye",
"type": "extended"
},
{
"kana": "でぉ",
"roumaji": "dho",
"type": "extended"
},
{
"kana": "でゃ",
"roumaji": "dya",
"type": "extended"
},
{
"kana": "でゅ",
"roumaji": "dyu",
"type": "extended"
},
{
"kana": "でょ",
"roumaji": "dyo",
"type": "extended"
},
{
"kana": "とぁ",
"roumaji": "twa",
"type": "extended"
},
{
"kana": "とぃ",
"roumaji": "twi",
"type": "extended"
},
{
"kana": "とぅ",
"roumaji": "tu",
"type": "extended"
},
{
"kana": "とぇ",
"roumaji": "twe",
"type": "extended"
},
{
"kana": "とぉ",
"roumaji": "two",
"type": "extended"
},
{
"kana": "どぁ",
"roumaji": "dwa",
"type": "extended"
},
{
"kana": "どぃ",
"roumaji": "dwi",
"type": "extended"
},
{
"kana": "どぅ",
"roumaji": "du",
"type": "extended"
},
{
"kana": "どぇ",
"roumaji": "dwe",
"type": "extended"
},
{
"kana": "どぉ",
"roumaji": "dwo",
"type": "extended"
},
{
"kana": "ふぁ",
"roumaji": "fa",
"type": "extended"
},
{
"kana": "ふぃ",
"roumaji": "fi",
"type": "extended"
},
{
"kana": "ほぅ",
"roumaji": "hu",
"type": "extended"
},
{
"kana": "ふぇ",
"roumaji": "fe",
"type": "extended"
},
{
"kana": "ふぉ",
"roumaji": "fo",
"type": "extended"
},
{
"kana": "ふゃ",
"roumaji": "fya",
"type": "extended"
},
{
"kana": "ふゅ",
"roumaji": "fyu",
"type": "extended"
},
{
"kana": "ふょ",
"roumaji": "fyo",
"type": "extended"
},
{
"kana": "りぃ",
"roumaji": "ryi",
"type": "extended"
},
{
"kana": "りぇ",
"roumaji": "rye",
"type": "extended"
},
{
"kana": "うぁ",
"roumaji": "(wa)",
"type": "extended"
},
{
"kana": "うぃ",
"roumaji": "wi",
"type": "extended"
},
{
"kana": "うぅ",
"roumaji": "(wu)",
"type": "extended"
},
{
"kana": "うぇ",
"roumaji": "we",
"type": "extended"
},
{
"kana": "うぉ",
"roumaji": "wo",
"type": "extended"
},
{
"kana": "うゃ",
"roumaji": "wya",
"type": "extended"
},
{
"kana": "うゅ",
"roumaji": "wyu",
"type": "extended"
},
{
"kana": "うょ",
"roumaji": "wyo",
"type": "extended"
},
{
"kana": "くぁ",
"roumaji": "kwa",
"type": "extended"
},
{
"kana": "くぃ",
"roumaji": "kwi",
"type": "extended"
},
{
"kana": "くぅ",
"roumaji": "kwu",
"type": "extended"
},
{
"kana": "くぇ",
"roumaji": "kwe",
"type": "extended"
},
{
"kana": "くぉ",
"roumaji": "kwo",
"type": "extended"
},
{
"kana": "ぐぁ",
"roumaji": "gwa",
"type": "extended"
},
{
"kana": "ぐぃ",
"roumaji": "gwi",
"type": "extended"
},
{
"kana": "ぐぅ",
"roumaji": "gwu",
"type": "extended"
},
{
"kana": "ぐぇ",
"roumaji": "gwe",
"type": "extended"
},
{
"kana": "ぐぉ",
"roumaji": "gwe",
"type": "extended"
},
{
"kana": "むぁ",
"roumaji": "mwa",
"type": "extended"
},
{
"kana": "むぃ",
"roumaji": "mwi",
"type": "extended"
},
{
"kana": "むぅ",
"roumaji": "mwu",
"type": "extended"
},
{
"kana": "むぇ",
"roumaji": "mwe",
"type": "extended"
},
{
"kana": "むぉ",
"roumaji": "mwo",
"type": "extended"
}
]

992
katakana.json Normal file
View File

@ -0,0 +1,992 @@
[
{
"kana": "ア",
"roumaji": "a",
"type": "gojuuon"
},
{
"kana": "イ",
"roumaji": "i",
"type": "gojuuon"
},
{
"kana": "ウ",
"roumaji": "u",
"type": "gojuuon"
},
{
"kana": "エ",
"roumaji": "e",
"type": "gojuuon"
},
{
"kana": "オ",
"roumaji": "o",
"type": "gojuuon"
},
{
"kana": "カ",
"roumaji": "ka",
"type": "gojuuon"
},
{
"kana": "キ",
"roumaji": "ki",
"type": "gojuuon"
},
{
"kana": "ク",
"roumaji": "ku",
"type": "gojuuon"
},
{
"kana": "ケ",
"roumaji": "ke",
"type": "gojuuon"
},
{
"kana": "コ",
"roumaji": "ko",
"type": "gojuuon"
},
{
"kana": "サ",
"roumaji": "sa",
"type": "gojuuon"
},
{
"kana": "シ",
"roumaji": "shi",
"type": "gojuuon"
},
{
"kana": "ス",
"roumaji": "su",
"type": "gojuuon"
},
{
"kana": "セ",
"roumaji": "se",
"type": "gojuuon"
},
{
"kana": "ソ",
"roumaji": "so",
"type": "gojuuon"
},
{
"kana": "タ",
"roumaji": "ta",
"type": "gojuuon"
},
{
"kana": "チ",
"roumaji": "chi",
"type": "gojuuon"
},
{
"kana": "ツ",
"roumaji": "tsu",
"type": "gojuuon"
},
{
"kana": "テ",
"roumaji": "te",
"type": "gojuuon"
},
{
"kana": "ト",
"roumaji": "to",
"type": "gojuuon"
},
{
"kana": "ナ",
"roumaji": "na",
"type": "gojuuon"
},
{
"kana": "ニ",
"roumaji": "ni",
"type": "gojuuon"
},
{
"kana": "ヌ",
"roumaji": "nu",
"type": "gojuuon"
},
{
"kana": "ネ",
"roumaji": "ne",
"type": "gojuuon"
},
{
"kana": "",
"roumaji": "no",
"type": "gojuuon"
},
{
"kana": "ハ",
"roumaji": "ha",
"type": "gojuuon"
},
{
"kana": "ヒ",
"roumaji": "hi",
"type": "gojuuon"
},
{
"kana": "フ",
"roumaji": "hu",
"type": "gojuuon"
},
{
"kana": "ヘ",
"roumaji": "he",
"type": "gojuuon"
},
{
"kana": "ホ",
"roumaji": "ho",
"type": "gojuuon"
},
{
"kana": "マ",
"roumaji": "ma",
"type": "gojuuon"
},
{
"kana": "ミ",
"roumaji": "mi",
"type": "gojuuon"
},
{
"kana": "ム",
"roumaji": "mu",
"type": "gojuuon"
},
{
"kana": "メ",
"roumaji": "me",
"type": "gojuuon"
},
{
"kana": "モ",
"roumaji": "mo",
"type": "gojuuon"
},
{
"kana": "ヤ",
"roumaji": "ya",
"type": "gojuuon"
},
{
"kana": "ユ",
"roumaji": "yu",
"type": "gojuuon"
},
{
"kana": "ヨ",
"roumaji": "yo",
"type": "gojuuon"
},
{
"kana": "ラ",
"roumaji": "ra",
"type": "gojuuon"
},
{
"kana": "リ",
"roumaji": "ri",
"type": "gojuuon"
},
{
"kana": "ル",
"roumaji": "ru",
"type": "gojuuon"
},
{
"kana": "レ",
"roumaji": "re",
"type": "gojuuon"
},
{
"kana": "ロ",
"roumaji": "ro",
"type": "gojuuon"
},
{
"kana": "ワ",
"roumaji": "wa",
"type": "gojuuon"
},
{
"kana": "ヲ",
"roumaji": "wo",
"type": "gojuuon"
},
{
"kana": "ン",
"roumaji": "n",
"type": "gojuuon"
},
{
"kana": "ガ",
"roumaji": "ga",
"type": "dakuon"
},
{
"kana": "ギ",
"roumaji": "gi",
"type": "dakuon"
},
{
"kana": "グ",
"roumaji": "gu",
"type": "dakuon"
},
{
"kana": "ゲ",
"roumaji": "ge",
"type": "dakuon"
},
{
"kana": "ゴ",
"roumaji": "go",
"type": "dakuon"
},
{
"kana": "ザ",
"roumaji": "za",
"type": "dakuon"
},
{
"kana": "ジ",
"roumaji": "ji",
"type": "dakuon"
},
{
"kana": "ズ",
"roumaji": "zu",
"type": "dakuon"
},
{
"kana": "ゼ",
"roumaji": "ze",
"type": "dakuon"
},
{
"kana": "ゾ",
"roumaji": "zo",
"type": "dakuon"
},
{
"kana": "ダ",
"roumaji": "da",
"type": "dakuon"
},
{
"kana": "ヂ",
"roumaji": "ji",
"type": "dakuon"
},
{
"kana": "ヅ",
"roumaji": "zu",
"type": "dakuon"
},
{
"kana": "デ",
"roumaji": "de",
"type": "dakuon"
},
{
"kana": "ド",
"roumaji": "do",
"type": "dakuon"
},
{
"kana": "バ",
"roumaji": "ba",
"type": "dakuon"
},
{
"kana": "ビ",
"roumaji": "bi",
"type": "dakuon"
},
{
"kana": "ブ",
"roumaji": "bu",
"type": "dakuon"
},
{
"kana": "ベ",
"roumaji": "be",
"type": "dakuon"
},
{
"kana": "ボ",
"roumaji": "bo",
"type": "dakuon"
},
{
"kana": "パ",
"roumaji": "pa",
"type": "handakuon"
},
{
"kana": "ピ",
"roumaji": "pi",
"type": "handakuon"
},
{
"kana": "プ",
"roumaji": "pu",
"type": "handakuon"
},
{
"kana": "ペ",
"roumaji": "pe",
"type": "handakuon"
},
{
"kana": "ポ",
"roumaji": "po",
"type": "handakuon"
},
{
"kana": "ッ",
"roumaji": "(pause)",
"type": "sokuon"
},
{
"kana": "キャ",
"roumaji": "kya",
"type": "youon"
},
{
"kana": "キュ",
"roumaji": "kyu",
"type": "youon"
},
{
"kana": "キョ",
"roumaji": "kyo",
"type": "youon"
},
{
"kana": "シャ",
"roumaji": "sha",
"type": "youon"
},
{
"kana": "シュ",
"roumaji": "shu",
"type": "youon"
},
{
"kana": "ショ",
"roumaji": "sho",
"type": "youon"
},
{
"kana": "チャ",
"roumaji": "cha",
"type": "youon"
},
{
"kana": "チュ",
"roumaji": "chu",
"type": "youon"
},
{
"kana": "チョ",
"roumaji": "cho",
"type": "youon"
},
{
"kana": "ニャ",
"roumaji": "nya",
"type": "youon"
},
{
"kana": "ニュ",
"roumaji": "nyu",
"type": "youon"
},
{
"kana": "ニョ",
"roumaji": "nyo",
"type": "youon"
},
{
"kana": "ヒャ",
"roumaji": "hya",
"type": "youon"
},
{
"kana": "ヒュ",
"roumaji": "hyu",
"type": "youon"
},
{
"kana": "ヒョ",
"roumaji": "hyo",
"type": "youon"
},
{
"kana": "ミャ",
"roumaji": "mya",
"type": "youon"
},
{
"kana": "ミュ",
"roumaji": "myu",
"type": "youon"
},
{
"kana": "ミョ",
"roumaji": "myo",
"type": "youon"
},
{
"kana": "リャ",
"roumaji": "rya",
"type": "youon"
},
{
"kana": "リュ",
"roumaji": "ryu",
"type": "youon"
},
{
"kana": "リョ",
"roumaji": "ryo",
"type": "youon"
},
{
"kana": "ギャ",
"roumaji": "gya",
"type": "youon"
},
{
"kana": "ギュ",
"roumaji": "gyu",
"type": "youon"
},
{
"kana": "ギョ",
"roumaji": "gyo",
"type": "youon"
},
{
"kana": "ジャ",
"roumaji": "ja",
"type": "youon"
},
{
"kana": "ジュ",
"roumaji": "ju",
"type": "youon"
},
{
"kana": "ジョ",
"roumaji": "jo",
"type": "youon"
},
{
"kana": "ビャ",
"roumaji": "bya",
"type": "youon"
},
{
"kana": "ビュ",
"roumaji": "byu",
"type": "youon"
},
{
"kana": "ビョ",
"roumaji": "byo",
"type": "youon"
},
{
"kana": "ピャ",
"roumaji": "pya",
"type": "youon"
},
{
"kana": "ピュ",
"roumaji": "pyu",
"type": "youon"
},
{
"kana": "ピョ",
"roumaji": "pyo",
"type": "youon"
},
{
"kana": "イィ",
"roumaji": "yi",
"type": "extended"
},
{
"kana": "イェ",
"roumaji": "ye",
"type": "extended"
},
{
"kana": "ヴァ",
"roumaji": "va",
"type": "extended"
},
{
"kana": "ヴィ",
"roumaji": "vi",
"type": "extended"
},
{
"kana": "ヴ",
"roumaji": "vu",
"type": "extended"
},
{
"kana": "ヴェ",
"roumaji": "ve",
"type": "extended"
},
{
"kana": "ヴォ",
"roumaji": "vo",
"type": "extended"
},
{
"kana": "ヴャ",
"roumaji": "vya",
"type": "extended"
},
{
"kana": "ヴュ",
"roumaji": "vyu",
"type": "extended"
},
{
"kana": "ヴョ",
"roumaji": "vyo",
"type": "extended"
},
{
"kana": "シェ",
"roumaji": "she",
"type": "extended"
},
{
"kana": "ジェ",
"roumaji": "je",
"type": "extended"
},
{
"kana": "チェ",
"roumaji": "che",
"type": "extended"
},
{
"kana": "スァ",
"roumaji": "swa",
"type": "extended"
},
{
"kana": "スィ",
"roumaji": "swi",
"type": "extended"
},
{
"kana": "スゥ",
"roumaji": "swu",
"type": "extended"
},
{
"kana": "スェ",
"roumaji": "swe",
"type": "extended"
},
{
"kana": "スォ",
"roumaji": "swo",
"type": "extended"
},
{
"kana": "スャ",
"roumaji": "sya",
"type": "extended"
},
{
"kana": "スュ",
"roumaji": "syu",
"type": "extended"
},
{
"kana": "スョ",
"roumaji": "syo",
"type": "extended"
},
{
"kana": "セィ",
"roumaji": "si",
"type": "extended"
},
{
"kana": "ズァ",
"roumaji": "zwa",
"type": "extended"
},
{
"kana": "ズィ",
"roumaji": "zwi",
"type": "extended"
},
{
"kana": "ズゥ",
"roumaji": "zwu",
"type": "extended"
},
{
"kana": "ズェ",
"roumaji": "zwe",
"type": "extended"
},
{
"kana": "ズォ",
"roumaji": "zwo",
"type": "extended"
},
{
"kana": "ズャ",
"roumaji": "zya",
"type": "extended"
},
{
"kana": "ズュ",
"roumaji": "zyu",
"type": "extended"
},
{
"kana": "ズョ",
"roumaji": "zyo",
"type": "extended"
},
{
"kana": "ゼィ",
"roumaji": "zi",
"type": "extended"
},
{
"kana": "ツァ",
"roumaji": "tsa",
"type": "extended"
},
{
"kana": "ツィ",
"roumaji": "tsi",
"type": "extended"
},
{
"kana": "ツェ",
"roumaji": "tse",
"type": "extended"
},
{
"kana": "ツォ",
"roumaji": "tso",
"type": "extended"
},
{
"kana": "テァ",
"roumaji": "tha",
"type": "extended"
},
{
"kana": "ティ",
"roumaji": "ti",
"type": "extended"
},
{
"kana": "テゥ",
"roumaji": "thu",
"type": "extended"
},
{
"kana": "テェ",
"roumaji": "tye",
"type": "extended"
},
{
"kana": "テォ",
"roumaji": "tho",
"type": "extended"
},
{
"kana": "テャ",
"roumaji": "tya",
"type": "extended"
},
{
"kana": "テュ",
"roumaji": "tyu",
"type": "extended"
},
{
"kana": "テョ",
"roumaji": "tyo",
"type": "extended"
},
{
"kana": "デァ",
"roumaji": "dha",
"type": "extended"
},
{
"kana": "ディ",
"roumaji": "di",
"type": "extended"
},
{
"kana": "デゥ",
"roumaji": "dhu",
"type": "extended"
},
{
"kana": "デェ",
"roumaji": "dye",
"type": "extended"
},
{
"kana": "デォ",
"roumaji": "dho",
"type": "extended"
},
{
"kana": "デャ",
"roumaji": "dya",
"type": "extended"
},
{
"kana": "デュ",
"roumaji": "dyu",
"type": "extended"
},
{
"kana": "デョ",
"roumaji": "dyo",
"type": "extended"
},
{
"kana": "トァ",
"roumaji": "twa",
"type": "extended"
},
{
"kana": "トィ",
"roumaji": "twi",
"type": "extended"
},
{
"kana": "トゥ",
"roumaji": "tu",
"type": "extended"
},
{
"kana": "トェ",
"roumaji": "twe",
"type": "extended"
},
{
"kana": "トォ",
"roumaji": "two",
"type": "extended"
},
{
"kana": "ドァ",
"roumaji": "dwa",
"type": "extended"
},
{
"kana": "ドィ",
"roumaji": "dwi",
"type": "extended"
},
{
"kana": "ドェ",
"roumaji": "dwe",
"type": "extended"
},
{
"kana": "ドォ",
"roumaji": "dwo",
"type": "extended"
},
{
"kana": "ファ",
"roumaji": "fa",
"type": "extended"
},
{
"kana": "フィ",
"roumaji": "fi",
"type": "extended"
},
{
"kana": "ホゥ",
"roumaji": "hu",
"type": "extended"
},
{
"kana": "フェ",
"roumaji": "fe",
"type": "extended"
},
{
"kana": "フォ",
"roumaji": "fo",
"type": "extended"
},
{
"kana": "フャ",
"roumaji": "fya",
"type": "extended"
},
{
"kana": "フュ",
"roumaji": "fyu",
"type": "extended"
},
{
"kana": "フョ",
"roumaji": "fyo",
"type": "extended"
},
{
"kana": "リィ",
"roumaji": "ryi",
"type": "extended"
},
{
"kana": "リェ",
"roumaji": "rye",
"type": "extended"
},
{
"kana": "ウァ",
"roumaji": "(wa)",
"type": "extended"
},
{
"kana": "ウィ",
"roumaji": "wi",
"type": "extended"
},
{
"kana": "ウゥ",
"roumaji": "(wu)",
"type": "extended"
},
{
"kana": "ウェ",
"roumaji": "we",
"type": "extended"
},
{
"kana": "ウォ",
"roumaji": "wo",
"type": "extended"
},
{
"kana": "ウャ",
"roumaji": "wya",
"type": "extended"
},
{
"kana": "ウュ",
"roumaji": "wyu",
"type": "extended"
},
{
"kana": "ウョ",
"roumaji": "wyo",
"type": "extended"
},
{
"kana": "クァ",
"roumaji": "kwa",
"type": "extended"
},
{
"kana": "クィ",
"roumaji": "kwi",
"type": "extended"
},
{
"kana": "クゥ",
"roumaji": "kwu",
"type": "extended"
},
{
"kana": "クェ",
"roumaji": "kwe",
"type": "extended"
},
{
"kana": "クォ",
"roumaji": "kwo",
"type": "extended"
},
{
"kana": "グァ",
"roumaji": "gwa",
"type": "extended"
},
{
"kana": "グィ",
"roumaji": "gwi",
"type": "extended"
},
{
"kana": "グゥ",
"roumaji": "gwu",
"type": "extended"
},
{
"kana": "グェ",
"roumaji": "gwe",
"type": "extended"
},
{
"kana": "グォ",
"roumaji": "gwe",
"type": "extended"
},
{
"kana": "ムァ",
"roumaji": "mwa",
"type": "extended"
},
{
"kana": "ムィ",
"roumaji": "mwi",
"type": "extended"
},
{
"kana": "ムゥ",
"roumaji": "mwu",
"type": "extended"
},
{
"kana": "ムェ",
"roumaji": "mwe",
"type": "extended"
},
{
"kana": "ムォ",
"roumaji": "mwo",
"type": "extended"
}
]

231
lib/JapaChar.pm Normal file
View File

@ -0,0 +1,231 @@
package JapaChar;
use v5.38.2;
use strict;
use warnings;
use feature 'signatures';
use Moo;
use Path::Tiny;
use Glib::Object::Introspection;
use YAML::PP;
use JapaChar::DB;
use JapaChar::Characters;
use Pango;
use Crypt::URandom qw( urandom );
use Glib::IO;
use constant PANGO_SCALE => 1024;
Glib::Object::Introspection->setup(
basename => 'Gtk',
version => '4.0',
package => 'Gtk',
);
Glib::Object::Introspection->setup(
basename => 'Gdk',
version => '4.0',
package => 'Gtk::Gdk',
);
Glib::Object::Introspection->setup(
basename => 'Gsk',
version => '4.0',
package => 'Gtk::Gsk',
);
has _counter => (
is => 'rw',
);
sub config($class) {
my $ypp = YAML::PP->new;
$ypp->load_file(''.path(__FILE__)->parent->parent->child('config.yml'));
}
sub _start_lesson($self, $window, $type = undef) {
$self->_counter(11);
$self->_new_challenge($window, $type);
}
sub _new_challenge($self, $window, $type = undef) {
$self->_counter($self->_counter - 1);
if ($self->_counter < 1) {
$self->_create_main_menu($window);
return;
}
my $rng = urandom(4);
$rng = unpack 'L', $rng;
$rng = ($rng % 100) + 1;
if ($rng > 50) {
$self->_new_challenge_romanji($window, $type);
return;
}
$self->_new_challenge_kana($window, $type);
}
sub _new_challenge_romanji($self, $window, $type = undef) {
my $show = 'romanji';
my $guess = 'kana';
$self->_new_challenge_generic_code($window, $type, $show, $guess);
}
sub _new_challenge_generic_code($self, $window, $type, $show, $guess) {
my $grid = $self->_create_grid_challenge;
my $char = JapaChar::Characters->new->next_char($type);
my $kana_label = $self->_get_label_featured_character($char->get($show));
$kana_label->set_halign('center');
$kana_label->set_valign('center');
$grid->attach($kana_label, 0, 0, 12, 1);
$window->set_child($grid);
my $incorrect_chars = JapaChar::Characters->new->get_4_incorrect_answers($char);
my @buttons;
my $continue_button = Gtk::Button->new_with_label('Continue');
$continue_button->set_valign('center');
$continue_button->set_halign('end');
$continue_button->set_sensitive(0);
my $on_answer = sub {
$continue_button->set_sensitive(1);
};
my $correct_answer_button = Gtk::ToggleButton->new_with_label($char->get($guess));
my $final_answer;
$correct_answer_button->signal_connect('clicked', sub {
$final_answer = $char->get($guess);
$on_answer->();
});
push @buttons, $correct_answer_button;
for my $char (@$incorrect_chars) {
my $incorrect_button = Gtk::ToggleButton->new_with_label($char->get($guess));
$incorrect_button->set_group($correct_answer_button);
$incorrect_button->signal_connect('clicked', sub {
$final_answer = $char->get($guess);
$on_answer->();
});
push @buttons, $incorrect_button;
}
@buttons = sort { rand() <=> rand() } @buttons;
my $box = Gtk::Box->new('horizontal', 10);
$box->set_valign('center');
$box->set_halign('center');
for my $button (@buttons) {
my $attr_list = Pango::AttrList->new;
my $size = Pango::AttrSize->new(42 * PANGO_SCALE);
$attr_list->insert($size);
$button->get_child->set_attributes($attr_list);
$box->append($button);
}
my $first_press_continue = 1;
$continue_button->signal_connect('clicked', sub {
if (!$first_press_continue) {
$self->_new_challenge($window, $type);
return;
}
$first_press_continue = 0;
my $label_feedback;
{
if ($final_answer eq $char->get($guess)) {
$label_feedback = Gtk::Label->new('You are doing it great.');
$label_feedback->add_css_class('success');
$char->success;
next;
}
$label_feedback = Gtk::Label->new('Meck!! The correct answer is ' . $char->get($guess));
$label_feedback->add_css_class('error');
$char->fail;
}
my $attr_list = Pango::AttrList->new;
my $size = Pango::AttrSize->new(18 * PANGO_SCALE);
$attr_list->insert($size);
$label_feedback->set_attributes($attr_list);
$grid->attach($label_feedback, 0, 2, 7, 1);
});
$grid->attach($box, 0, 1, 12, 1);
my $attr_list = Pango::AttrList->new;
my $size = Pango::AttrSize->new(25 * PANGO_SCALE);
$attr_list->insert($size);
$continue_button->get_child->set_attributes($attr_list);
$grid->attach($continue_button, 6, 2, 5, 1);
}
sub _new_challenge_kana($self, $window, $type = undef) {
my $show = 'kana';
my $guess = 'romanji';
$self->_new_challenge_generic_code($window, $type, $show, $guess);
}
sub _create_grid_challenge($self) {
my $grid = Gtk::Grid->new;
$grid->set_column_homogeneous(1);
$grid->set_row_homogeneous(1);
return $grid;
}
sub _get_label_featured_character($self, $text) {
my $label = Gtk::Label->new($text);
my $attr_list = Pango::AttrList->new;
my $size = Pango::AttrSize->new(72 * PANGO_SCALE);
$attr_list->insert($size);
$label->set_attributes($attr_list);
$label->set_halign('center');
return $label;
}
sub _create_main_menu($self, $window) {
my $grid = Gtk::Grid->new;
my $button_start_basic_lesson = Gtk::Button->new_with_label('Basic Characters');
$button_start_basic_lesson->signal_connect('clicked', sub {
$self->_start_lesson($window);
});
$grid->set_column_homogeneous(1);
$grid->set_row_homogeneous(1);
my $button_start_hiragana_lesson = Gtk::Button->new_with_label('Hiragana');
$button_start_hiragana_lesson->signal_connect('clicked', sub {
$self->_start_lesson($window, 'hiragana');
});
my $button_start_katakana_lesson = Gtk::Button->new_with_label('Katakana');
$button_start_katakana_lesson->signal_connect('clicked', sub {
$self->_start_lesson($window, 'katakana');
});
for my $button ($button_start_basic_lesson, $button_start_hiragana_lesson, $button_start_katakana_lesson) {
my $attr_list = Pango::AttrList->new;
my $size = Pango::AttrSize->new(25 * PANGO_SCALE);
$attr_list->insert($size);
$button->get_child->set_attributes($attr_list);
}
my $box = Gtk::Box->new('horizontal', 10);
$grid->attach($button_start_basic_lesson, 0, 0, 5, 1);
$button_start_basic_lesson->set_valign('end');
$button_start_basic_lesson->set_halign('center');
$box->set_margin_top(40);
$box->append($button_start_hiragana_lesson);
$box->append($button_start_katakana_lesson);
$box->set_valign('start');
$box->set_halign('center');
$grid->attach($box, 0, 1, 5, 1);
$window->set_child($grid);
}
sub _application_start($self, $app) {
my $main_window = Gtk::ApplicationWindow->new($app);
$main_window->set_title('JapaChar');
$main_window->set_default_size(600, 600);
my $display = $main_window->get_property('display');
my $css_provider = Gtk::CssProvider->new;
$css_provider->load_from_path(path(__FILE__)->parent->parent->child('styles.css')->absolute);
Gtk::StyleContext::add_provider_for_display($display, $css_provider, 'priority-fallback');
$self->_create_main_menu($main_window);
$main_window->present;
}
sub start($self) {
my $app = Gtk::Application->new('me.sergiotarxz.japchar', 'default-flags');
$app->signal_connect('activate' => sub {
$self->_application_start($app);
});
$app->run;
}
1;

146
lib/JapaChar/Characters.pm Normal file
View File

@ -0,0 +1,146 @@
package JapaChar::Characters;
use v5.38.2;
use strict;
use warnings;
use Moo;
use Path::Tiny;
use JSON;
use Data::Dumper;
my $option_populated = 'populated_basic_characters';
require JapaChar::DB;
require JapaChar::Schema;
sub populate_basic_characters($self) {
my $dbh = JapaChar::DB->connect;
my $result = $dbh->selectrow_hashref(
'SELECT value
FROM options
WHERE name = ?', {}, $option_populated
);
if ( defined $result && $result->{value} ) {
return;
}
$self->_populate_type('hiragana');
$self->_populate_type('katakana');
$dbh->do( 'INSERT INTO options (name, value) VALUES (?, ?);',
undef, $option_populated, 1 );
}
sub _populate_type( $self, $type ) {
my $basic_character_resultset =
JapaChar::Schema->Schema->resultset('BasicCharacter');
for my $char ( @{ $self->_get_characters_of_type($type) } ) {
my $kana = $char->{kana};
my $romanji = $char->{roumaji};
next if $romanji =~ /pause/i;
$basic_character_resultset->new(
{ value => $kana, romanji => $romanji, type => $type, } )->insert;
}
}
sub _get_characters_of_type( $self, $type ) {
my $current_file = path __FILE__;
my $array =
from_json( $current_file->parent->parent->parent->child("$type.json")
->slurp_utf8 );
return $array;
}
sub get_4_incorrect_answers( $self, $char ) {
my $basic_character_resultset =
JapaChar::Schema->Schema->resultset('BasicCharacter');
my @bad_answers = $basic_character_resultset->search(
{
type => $char->type,
value => { '!=', $char->value },
romanji => { '!=', $char->romanji },
-bool => 'started',
},
{
order_by => { -asc => \'RANDOM()' },
rows => 4,
}
);
return \@bad_answers;
}
sub next_review_char( $self, $type = undef ) {
my $basic_character_resultset =
JapaChar::Schema->Schema->resultset('BasicCharacter');
my @chars = $basic_character_resultset->search(
{
score => { '>=' => 100 },
(
( defined $type ) ? ( type => $type, ) : ()
)
},
{
order_by => { -asc => \'RANDOM()' },
rows => 1
}
);
if ( !@chars ) {
return;
}
return $chars[0];
}
sub next_char( $self, $type = undef ) {
my $next_review = $self->next_review_char($type);
my $next_learning = $self->next_learning_char($type);
if ( !defined $next_review ) {
return $next_learning;
}
my $rng = int( rand(100) ) + 1;
if ( $rng > 20 ) {
return $next_learning;
}
return $next_review;
}
sub next_learning_char( $self, $type = undef ) {
$self->populate_basic_characters;
my $basic_character_resultset =
JapaChar::Schema->Schema->resultset('BasicCharacter');
my @candidate_chars = $self->_retrieve_started_chars_not_finished($type);
if ( @candidate_chars < 5 ) {
my @new_chars = $basic_character_resultset->search(
{
-not_bool => 'started',
(
( defined $type ) ? ( type => $type, ) : ()
)
},
{
order_by => { -asc => 'id' },
rows => 5 - scalar @candidate_chars,
}
);
for my $char (@new_chars) {
$char->update( { started => 1 } );
}
@candidate_chars = $self->_retrieve_started_chars_not_finished($type);
}
my $char = $candidate_chars[ int( rand( scalar @candidate_chars ) ) ];
return $char;
}
sub _retrieve_started_chars_not_finished( $self, $type ) {
my $basic_character_resultset =
JapaChar::Schema->Schema->resultset('BasicCharacter');
return $basic_character_resultset->search(
{
(
( defined $type ) ? ( type => $type, ) : ()
),
score => { '<' => 100 },
-bool => 'started',
}
);
}
1;

109
lib/JapaChar/DB.pm Normal file
View File

@ -0,0 +1,109 @@
package JapaChar::DB;
use v5.38.2;
use strict;
use warnings;
use feature 'signatures';
use DBI;
use DBD::SQLite;
use JapaChar::DB::Migrations;
use Path::Tiny;
use Data::Dumper;
my $dbh;
sub connect {
if ( defined $dbh ) {
return $dbh;
}
my $class = shift;
require JapaChar;
my $app = JapaChar->new;
my $config = $app->config;
my $db_path = $class->_db_path;
$dbh = DBI->connect(
'dbi:SQLite:dbname='.$db_path,
undef, undef,
{
RaiseError => 1,
},
);
$class->_migrate($dbh);
return $dbh;
}
sub _db_path($class) {
my $home = $ENV{HOME};
my $db_path = '';
$db_path = $home . '/' if $home;
$db_path .= '.japachar/db.sqlite';
path($db_path)->parent->mkpath;
return $db_path;
}
sub _migrate {
my $class = shift;
my $dbh = shift;
local $dbh->{RaiseError} = 0;
local $dbh->{PrintError} = 0;
my @migrations = JapaChar::DB::Migrations::MIGRATIONS();
if ( $class->get_current_migration($dbh) > @migrations ) {
warn "Something happened there, wrong migration number.";
}
if ( $class->get_current_migration($dbh) >= @migrations ) {
say STDERR "Migrations already applied.";
return;
}
$class->_apply_migrations( $dbh, \@migrations );
}
sub _apply_migrations {
my $class = shift;
my $dbh = shift;
my $migrations = shift;
for (
my $i = $class->get_current_migration($dbh) ;
$i < @$migrations ;
$i++
)
{
local $dbh->{RaiseError} = 1;
my $current_migration = $migrations->[$i];
my $migration_number = $i + 1;
$class->_apply_migration( $dbh, $current_migration, $migration_number );
}
}
sub _apply_migration {
my $class = shift;
my $dbh = shift;
my $current_migration = shift;
my $migration_number = shift;
{
if (ref $current_migration eq 'CODE') {
$current_migration->($dbh);
next;
}
$dbh->do($current_migration);
}
$dbh->do( <<'EOF', undef, 'current_migration', $migration_number );
INSERT INTO options
VALUES ($1, $2)
ON CONFLICT (name) DO
UPDATE SET value = $2;
EOF
}
sub get_current_migration {
my $class = shift;
my $dbh = shift;
my $result = $dbh->selectrow_hashref( <<'EOF', undef, 'current_migration' );
select value from options where name = ?;
EOF
return int( $result->{value} // 0 );
}
1;

View File

@ -0,0 +1,28 @@
package JapaChar::DB::Migrations;
use v5.34.1;
use strict;
use warnings;
use utf8;
use feature 'signatures';
sub MIGRATIONS {
return (
'CREATE TABLE options (
name TEXT PRIMARY KEY,
value TEXT
)',
'CREATE TABLE basic_characters (
id INTEGER PRIMARY KEY,
value TEXT NOT NULL UNIQUE,
romanji TEXT NOT NULL,
type TEXT NOT NULL,
started BOOLEAN NOT NULL DEFAULT 0,
score INTEGER NOT NULL DEFAULT 0,
consecutive_success INTEGER NOT NULL DEFAULT 0
);',
);
}
1;

32
lib/JapaChar/Schema.pm Normal file
View File

@ -0,0 +1,32 @@
package JapaChar::Schema;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use parent 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces();
my $schema;
sub Schema ($class) {
if ( !defined $schema ) {
require JapaChar::DB;
JapaChar::DB->connect;
my $db_path = JapaChar::DB->_db_path;
my $user = undef;
my $password = undef;
# Undef is perfectly fine for username and password.
$schema = $class->connect(
'dbi:SQLite:dbname='.$db_path, $user, $password,
{
}
);
}
return $schema;
}
1;

View File

@ -0,0 +1,96 @@
package JapaChar::Schema::Result::BasicCharacter;
use v5.38.2;
use strict;
use warnings;
use feature 'signatures';
use parent 'DBIx::Class::Core';
use Encode qw/decode/;
__PACKAGE__->table('basic_characters');
__PACKAGE__->add_columns(
id => {
data_type => 'INTEGER',
is_auto_increment => 1,
},
value => {
data_type => 'TEXT',
is_nullable => 0,
accessor => '_value',
},
romanji => {
data_type => 'TEXT',
is_nullable => 0,
},
type => {
data_type => 'TEXT',
is_nullable => 0,
},
started => {
data_type => 'BOOLEAN',
is_nullable => 1,
},
score => {
data_type => 'INTEGER',
is_nullable => 1,
},
consecutive_success => {
data_type => 'INTEGER',
is_nullable => 1,
},
);
sub value($self, $value = undef) {
if (defined $value) {
$self->_value($value);
}
return decode 'utf-8', $self->_value;
}
__PACKAGE__->set_primary_key('id');
sub fail($self) {
my $score = $self->score;
my $consecutive_success = 0;
$score -= 25;
if ( $score < 0 ) {
$score = 0;
}
$self->update(
{
score => $score,
consecutive_success => 0
}
);
}
sub get($self, $what) {
if ($what eq 'kana') {
return $self->value;
}
if ($what eq 'romanji') {
return $self->romanji;
}
return;
}
sub success($self) {
my $score = $self->score;
my $consecutive_success = $self->consecutive_success + 1;
$score += 5 + 10 * $consecutive_success;
if ( $score > 130 ) {
$score = 130;
}
$self->update(
{
score => $score,
consecutive_success => $consecutive_success,
}
);
}
1;

11
scripts/japchar.pl Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env perl
use v5.38.2;
use File::Basename;
use lib dirname(dirname(__FILE__)).'/lib';
use JapaChar;
JapaChar->new->start;

7
styles.css Normal file
View File

@ -0,0 +1,7 @@
.error {
color: red;
}
.success {
color: green;
}