jlpt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.ja.md +246 -0
  4. data/README.md +224 -0
  5. data/assets/logo.png +0 -0
  6. data/bin/jlpt +7 -0
  7. data/lib/jlpt/active_model/validator.rb +43 -0
  8. data/lib/jlpt/analyzers/comparer.rb +44 -0
  9. data/lib/jlpt/analyzers/conjugation.rb +60 -0
  10. data/lib/jlpt/analyzers/corpus_stats.rb +38 -0
  11. data/lib/jlpt/analyzers/dokkai.rb +53 -0
  12. data/lib/jlpt/analyzers/exam_readiness.rb +52 -0
  13. data/lib/jlpt/analyzers/grammar.rb +50 -0
  14. data/lib/jlpt/analyzers/jukugo.rb +46 -0
  15. data/lib/jlpt/analyzers/loanwords.rb +54 -0
  16. data/lib/jlpt/analyzers/onomatopeia.rb +29 -0
  17. data/lib/jlpt/analyzers/pitch_accent.rb +51 -0
  18. data/lib/jlpt/analyzers/pos_profiler.rb +47 -0
  19. data/lib/jlpt/analyzers/simplifier.rb +48 -0
  20. data/lib/jlpt/analyzers/style_profiler.rb +62 -0
  21. data/lib/jlpt/analyzers/syntax.rb +43 -0
  22. data/lib/jlpt/cli.rb +121 -0
  23. data/lib/jlpt/core/analysis_result.rb +45 -0
  24. data/lib/jlpt/core/analyzer.rb +58 -0
  25. data/lib/jlpt/core/cache_manager.rb +90 -0
  26. data/lib/jlpt/core/config.rb +45 -0
  27. data/lib/jlpt/core/dictionary.rb +113 -0
  28. data/lib/jlpt/core/engine.rb +142 -0
  29. data/lib/jlpt/core/kanji.rb +54 -0
  30. data/lib/jlpt/core/kanji_details.rb +60 -0
  31. data/lib/jlpt/core/preprocessor.rb +48 -0
  32. data/lib/jlpt/core/tokenizer.rb +64 -0
  33. data/lib/jlpt/core/vocab_classifier.rb +48 -0
  34. data/lib/jlpt/data/kanji.json +2213 -0
  35. data/lib/jlpt/data/vocab.json +50298 -0
  36. data/lib/jlpt/formatters/batch_processor.rb +41 -0
  37. data/lib/jlpt/formatters/exporter.rb +55 -0
  38. data/lib/jlpt/formatters/furigana.rb +69 -0
  39. data/lib/jlpt/formatters/reporter.rb +72 -0
  40. data/lib/jlpt/version.rb +5 -0
  41. data/lib/jlpt.rb +50 -0
  42. metadata +184 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Batch Text, Subtitle, and Multi-Chapter Document Processor.
5
+ #
6
+ # Processes multiple text sections or chapters, generating difficulty roadmaps
7
+ # and per-section metric summaries.
8
+ #
9
+ module BatchProcessor
10
+ class << self
11
+ # Process a collection of text sections or chapters
12
+ #
13
+ # @param sections [Hash, Array<Hash>, Array<String>] sections mapping or list
14
+ # @return [Array<Hash>] roadmap of processed section difficulty metrics
15
+ def process(sections)
16
+ return [] if sections.nil? || sections.empty?
17
+
18
+ items = normalize_sections(sections)
19
+ items.map do |name, text|
20
+ res = JLPT.analyze(text)
21
+ { name: name, recommended_level: res.recommended_level, score: res.score,
22
+ word_count: res.word_count, sentence_count: res.sentence_count }
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def normalize_sections(sections)
29
+ if sections.is_a?(Hash)
30
+ sections.to_a
31
+ elsif sections.is_a?(Array)
32
+ sections.map.with_index do |sec, idx|
33
+ sec.is_a?(Hash) ? [sec[:name] || "Section #{idx + 1}", sec[:text] || ''] : ["Section #{idx + 1}", sec.to_s]
34
+ end
35
+ else
36
+ []
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Learner Tools and Anki Flashcard Exporter.
5
+ #
6
+ # Identifies new / unknown vocabulary above a learner's current target level
7
+ # and exports flashcards in Anki TSV/CSV format.
8
+ #
9
+ module Exporter
10
+ LEVEL_ORDER = { n5: 1, n4: 2, n3: 3, n2: 4, n1: 5, non_jlpt: 6, out_of_jlpt: 6 }.freeze
11
+
12
+ class << self
13
+ # Extract vocabulary words that exceed the learner's current level
14
+ #
15
+ # @param text [String] Japanese text
16
+ # @param target_level [Symbol, String] learner's mastered level (:n5, :n4, :n3, :n2)
17
+ # @return [Array<Hash>] array of unknown word hashes
18
+ def find_unknown(text, target_level: :n4)
19
+ target_val = LEVEL_ORDER[target_level.to_s.downcase.to_sym] || 2
20
+ classified = VocabClassifier.classify(text)
21
+
22
+ unknown = classified.flat_map do |lvl, words|
23
+ extract_words_above_target(lvl, words, target_val)
24
+ end
25
+ unknown.uniq { |item| item[:word] }
26
+ end
27
+
28
+ # Export unknown words as Anki tab-separated values (TSV)
29
+ #
30
+ # @param text [String] Japanese text
31
+ # @param target_level [Symbol, String]
32
+ # @return [String] TSV formatted string
33
+ def to_anki_tsv(text, target_level: :n4)
34
+ items = find_unknown(text, target_level: target_level)
35
+ return '' if items.empty?
36
+
37
+ lines = items.map do |item|
38
+ "#{item[:word]}\t#{item[:word]}[#{item[:reading]}]\t#{item[:level].to_s.upcase}"
39
+ end
40
+ lines.join("\n")
41
+ end
42
+
43
+ private
44
+
45
+ def extract_words_above_target(lvl, words, target_val)
46
+ lvl_val = LEVEL_ORDER[lvl] || 6
47
+ return [] unless lvl_val > target_val
48
+
49
+ words.map do |w|
50
+ { word: w, level: lvl, reading: Dictionary.reading_for(w) || w }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Furigana Generator and Romaji Transliteration Engine.
5
+ #
6
+ # Attaches Hiragana readings to Kanji words in HTML (<ruby>) or Markdown/Anki format,
7
+ # and provides Hepburn Romaji transliteration.
8
+ #
9
+ module Furigana
10
+ KANJI_REGEX = /[\u4E00-\u9FFF]/.freeze
11
+
12
+ class << self
13
+ # Render text with Furigana annotations
14
+ #
15
+ # @param text [String] Japanese input text
16
+ # @param format [Symbol] :html or :markdown (:anki)
17
+ # @return [String] formatted string
18
+ def render(text, format: :html)
19
+ tokens = Tokenizer.tokenize(text)
20
+ return '' if tokens.empty?
21
+
22
+ tokens.map { |token| render_token(token, format: format) }.join
23
+ end
24
+
25
+ # Transliterate Katakana/Hiragana to Hepburn Romaji
26
+ #
27
+ # @param text [String] Japanese text
28
+ # @return [String] Romaji string
29
+ def to_romaji(text)
30
+ tokens = Tokenizer.tokenize(text)
31
+ return '' if tokens.empty?
32
+
33
+ tokens.map { |t| kana_to_romaji(t[:reading]) }.join(' ').strip
34
+ end
35
+
36
+ private
37
+
38
+ def render_token(token, format:)
39
+ surface = token[:surface]
40
+ return surface unless surface.match?(KANJI_REGEX)
41
+
42
+ reading = determine_furigana_reading(surface, token[:reading])
43
+ return surface if reading.empty?
44
+
45
+ format == :html ? "<ruby><rb>#{surface}</rb><rt>#{reading}</rt></ruby>" : "#{surface}[#{reading}]"
46
+ end
47
+
48
+ def determine_furigana_reading(surface, reading)
49
+ if reading && reading != surface
50
+ katakana_to_hiragana(reading)
51
+ else
52
+ Dictionary.reading_for(surface) || ''
53
+ end
54
+ end
55
+
56
+ def katakana_to_hiragana(str)
57
+ str.tr('ァ-ン', 'ぁ-ん')
58
+ end
59
+
60
+ def kana_to_romaji(str)
61
+ return '' if str.nil?
62
+
63
+ str.tr('がぎぐげござじずぜぞだぢづでどばびぶべぼ', 'gagigugegozajizuzedodidudedobabibubebo')
64
+ .tr('あいうえおかきくけこさしすせそたちつてと', 'aiueokakikukesashisusesotachitsuteto')
65
+ .tr('なにぬねのはひふへほまみむめもやゆよらりるれろわをん', 'naninunenohahifuhemomamimumemoyayuyorarirurerowawon')
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Interactive Visual Reporter.
5
+ #
6
+ # Generates self-contained HTML reports with modern glassmorphism UI,
7
+ # difficulty level badges, progress bars, and GitHub-Flavored Markdown summaries.
8
+ #
9
+ module Reporter
10
+ class << self
11
+ # Render a beautiful, standalone HTML report for an AnalysisResult
12
+ #
13
+ # @param result [JLPT::AnalysisResult]
14
+ # @return [String] HTML string
15
+ def render_html(result)
16
+ furigana = Furigana.render(result.text, format: :html)
17
+ lvl = result.recommended_level.to_s.upcase
18
+
19
+ <<~HTML
20
+ <!DOCTYPE html>
21
+ <html lang="ja">
22
+ <head><meta charset="UTF-8"><title>JLPT Analysis Report - #{lvl}</title>#{html_style_block}</head>
23
+ <body>#{html_header_card(result)}<div class="card"><h2>Annotated Text</h2><div class="reading">#{furigana}</div></div></body>
24
+ </html>
25
+ HTML
26
+ end
27
+
28
+ # Render a Markdown report table
29
+ #
30
+ # @param result [JLPT::AnalysisResult]
31
+ # @return [String] Markdown string
32
+ def render_markdown(result)
33
+ <<~MARKDOWN
34
+ # JLPT Analysis Report
35
+
36
+ | Metric | Value |
37
+ | :--- | :--- |
38
+ | **Recommended Level** | #{result.recommended_level.to_s.upcase} |
39
+ | **Difficulty Score** | #{result.score} / 100 |
40
+ | **Kanji Density** | #{(result.kanji_density * 100).round(1)}% |
41
+ | **Word Count** | #{result.word_count} |
42
+ | **Sentence Count** | #{result.sentence_count} |
43
+ MARKDOWN
44
+ end
45
+
46
+ private
47
+
48
+ def html_style_block
49
+ <<~CSS
50
+ <style>
51
+ body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
52
+ .card { background: rgba(30, 41, 59, 0.8); border: 1px solid #334155; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; backdrop-filter: blur(10px); }
53
+ .badge { display: inline-block; padding: 0.5rem 1rem; border-radius: 9999px; background: #6366f1; color: #fff; font-weight: bold; font-size: 1.25rem; }
54
+ .reading { line-height: 2.2; font-size: 1.2rem; }
55
+ rt { font-size: 0.75rem; color: #818cf8; }
56
+ </style>
57
+ CSS
58
+ end
59
+
60
+ def html_header_card(result)
61
+ <<~CARD
62
+ <div class="card">
63
+ <h1>JLPT Difficulty Report</h1>
64
+ <p>Recommended Level: <span class="badge">#{result.recommended_level.to_s.upcase}</span></p>
65
+ <p>Difficulty Score: <strong>#{result.score} / 100</strong></p>
66
+ <p>Kanji Density: <strong>#{(result.kanji_density * 100).round(1)}%</strong> | Sentences: <strong>#{result.sentence_count}</strong></p>
67
+ </div>
68
+ CARD
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ VERSION = '1.0.0'
5
+ end
data/lib/jlpt.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jlpt/version'
4
+ require_relative 'jlpt/core/config'
5
+ require_relative 'jlpt/core/engine'
6
+ require_relative 'jlpt/core/dictionary'
7
+ require_relative 'jlpt/core/preprocessor'
8
+ require_relative 'jlpt/core/tokenizer'
9
+ require_relative 'jlpt/core/kanji'
10
+ require_relative 'jlpt/core/kanji_details'
11
+ require_relative 'jlpt/core/vocab_classifier'
12
+ require_relative 'jlpt/core/analysis_result'
13
+ require_relative 'jlpt/core/cache_manager'
14
+ require_relative 'jlpt/core/analyzer'
15
+ require_relative 'jlpt/analyzers/grammar'
16
+ require_relative 'jlpt/analyzers/style_profiler'
17
+ require_relative 'jlpt/analyzers/pitch_accent'
18
+ require_relative 'jlpt/analyzers/conjugation'
19
+ require_relative 'jlpt/analyzers/corpus_stats'
20
+ require_relative 'jlpt/analyzers/dokkai'
21
+ require_relative 'jlpt/analyzers/jukugo'
22
+ require_relative 'jlpt/analyzers/loanwords'
23
+ require_relative 'jlpt/analyzers/comparer'
24
+ require_relative 'jlpt/analyzers/onomatopeia'
25
+ require_relative 'jlpt/analyzers/pos_profiler'
26
+ require_relative 'jlpt/analyzers/exam_readiness'
27
+ require_relative 'jlpt/analyzers/syntax'
28
+ require_relative 'jlpt/analyzers/simplifier'
29
+ require_relative 'jlpt/formatters/furigana'
30
+ require_relative 'jlpt/formatters/exporter'
31
+ require_relative 'jlpt/formatters/reporter'
32
+ require_relative 'jlpt/formatters/batch_processor'
33
+ require_relative 'jlpt/cli'
34
+ require_relative 'jlpt/active_model/validator'
35
+
36
+ # Main entry point for the JLPT Japanese text analysis library.
37
+ #
38
+ module JLPT
39
+ class Error < StandardError; end
40
+
41
+ class << self
42
+ # Analyze Japanese text and return difficulty metrics & JLPT recommendation
43
+ #
44
+ # @param text [String] Japanese text
45
+ # @return [JLPT::AnalysisResult]
46
+ def analyze(text)
47
+ Analyzer.analyze(text)
48
+ end
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jlpt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jakub Polak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: natto
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.57'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.57'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.22'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.22'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ description: Ruby library for Japanese morphological analysis, JLPT N5-N1 difficulty
112
+ scoring, furigana generation, kanji extraction, and pitch accent profiling.
113
+ email:
114
+ - jakub.polak.vz@gmail.com
115
+ executables:
116
+ - jlpt
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - LICENSE
121
+ - README.ja.md
122
+ - README.md
123
+ - assets/logo.png
124
+ - bin/jlpt
125
+ - lib/jlpt.rb
126
+ - lib/jlpt/active_model/validator.rb
127
+ - lib/jlpt/analyzers/comparer.rb
128
+ - lib/jlpt/analyzers/conjugation.rb
129
+ - lib/jlpt/analyzers/corpus_stats.rb
130
+ - lib/jlpt/analyzers/dokkai.rb
131
+ - lib/jlpt/analyzers/exam_readiness.rb
132
+ - lib/jlpt/analyzers/grammar.rb
133
+ - lib/jlpt/analyzers/jukugo.rb
134
+ - lib/jlpt/analyzers/loanwords.rb
135
+ - lib/jlpt/analyzers/onomatopeia.rb
136
+ - lib/jlpt/analyzers/pitch_accent.rb
137
+ - lib/jlpt/analyzers/pos_profiler.rb
138
+ - lib/jlpt/analyzers/simplifier.rb
139
+ - lib/jlpt/analyzers/style_profiler.rb
140
+ - lib/jlpt/analyzers/syntax.rb
141
+ - lib/jlpt/cli.rb
142
+ - lib/jlpt/core/analysis_result.rb
143
+ - lib/jlpt/core/analyzer.rb
144
+ - lib/jlpt/core/cache_manager.rb
145
+ - lib/jlpt/core/config.rb
146
+ - lib/jlpt/core/dictionary.rb
147
+ - lib/jlpt/core/engine.rb
148
+ - lib/jlpt/core/kanji.rb
149
+ - lib/jlpt/core/kanji_details.rb
150
+ - lib/jlpt/core/preprocessor.rb
151
+ - lib/jlpt/core/tokenizer.rb
152
+ - lib/jlpt/core/vocab_classifier.rb
153
+ - lib/jlpt/data/kanji.json
154
+ - lib/jlpt/data/vocab.json
155
+ - lib/jlpt/formatters/batch_processor.rb
156
+ - lib/jlpt/formatters/exporter.rb
157
+ - lib/jlpt/formatters/furigana.rb
158
+ - lib/jlpt/formatters/reporter.rb
159
+ - lib/jlpt/version.rb
160
+ homepage: https://github.com/kupolak/jlpt
161
+ licenses:
162
+ - MIT
163
+ metadata:
164
+ rubygems_mfa_required: 'true'
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 2.7.0
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ requirements: []
180
+ rubygems_version: 3.5.22
181
+ signing_key:
182
+ specification_version: 4
183
+ summary: Japanese text analyzer and difficulty classifier for JLPT levels N5-N1
184
+ test_files: []