diff --git a/Build.PL b/Build.PL new file mode 100755 index 0000000..cab7dec --- /dev/null +++ b/Build.PL @@ -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 ', + 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; diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/hiragana.json b/hiragana.json new file mode 100644 index 0000000..12caf4a --- /dev/null +++ b/hiragana.json @@ -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" + } +] diff --git a/katakana.json b/katakana.json new file mode 100644 index 0000000..222970e --- /dev/null +++ b/katakana.json @@ -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" + } +] diff --git a/lib/JapaChar.pm b/lib/JapaChar.pm new file mode 100644 index 0000000..cf6c2b9 --- /dev/null +++ b/lib/JapaChar.pm @@ -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; diff --git a/lib/JapaChar/Characters.pm b/lib/JapaChar/Characters.pm new file mode 100644 index 0000000..dbf98df --- /dev/null +++ b/lib/JapaChar/Characters.pm @@ -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; diff --git a/lib/JapaChar/DB.pm b/lib/JapaChar/DB.pm new file mode 100644 index 0000000..491c9cd --- /dev/null +++ b/lib/JapaChar/DB.pm @@ -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; diff --git a/lib/JapaChar/DB/Migrations.pm b/lib/JapaChar/DB/Migrations.pm new file mode 100644 index 0000000..6238be2 --- /dev/null +++ b/lib/JapaChar/DB/Migrations.pm @@ -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; diff --git a/lib/JapaChar/Schema.pm b/lib/JapaChar/Schema.pm new file mode 100644 index 0000000..afc8707 --- /dev/null +++ b/lib/JapaChar/Schema.pm @@ -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; diff --git a/lib/JapaChar/Schema/Result/BasicCharacter.pm b/lib/JapaChar/Schema/Result/BasicCharacter.pm new file mode 100644 index 0000000..81f1fb1 --- /dev/null +++ b/lib/JapaChar/Schema/Result/BasicCharacter.pm @@ -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; diff --git a/scripts/japchar.pl b/scripts/japchar.pl new file mode 100644 index 0000000..b5bd878 --- /dev/null +++ b/scripts/japchar.pl @@ -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; diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..1fe6829 --- /dev/null +++ b/styles.css @@ -0,0 +1,7 @@ +.error { + color: red; +} + +.success { + color: green; +}