jp_quest 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 897794dabb7c1b722d1bcad7235c64e04c2ca449060278982774c7bcefc5f824
4
+ data.tar.gz: baaf02d4c32266a22178db399701b1b2bf5e0512401120e1a5a551de8fecfafd
5
+ SHA512:
6
+ metadata.gz: 57396c508bc82648d1f18427e09445a7c8e82edcf21a4376b086c6fbea3845576907c6e268c09a37d084f3efaf908503b763887f0d22838e38dc9619c54e950a
7
+ data.tar.gz: 2e573574cf3731f8aefe7567aa57bc1fcc43b0b75fedc74f81455e4f1cc50dfdbb6168a62f1a1d2f6ae1e93e25e6368bcb936274dcc3522a0a33c418bf810c5d
data/.licensed.yml ADDED
@@ -0,0 +1,10 @@
1
+ name: "jp_quest"
2
+
3
+ allowed:
4
+ - mit
5
+ - other
6
+ - wtfpl
7
+ - apache-2.0
8
+
9
+ # bundle exec licensed cache
10
+ # bundle exec licensed status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,49 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ Exclude:
5
+ - "bin/**/*"
6
+
7
+ Style/StringLiterals:
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ EnforcedStyle: double_quotes
12
+
13
+ Style/Documentation:
14
+ Enabled: false
15
+
16
+ Style/ParallelAssignment:
17
+ Enabled: false
18
+
19
+ Metrics/MethodLength:
20
+ CountComments: true
21
+ Max: 20
22
+
23
+ Lint/ScriptPermission:
24
+ Enabled: false
25
+
26
+ Style/FetchEnvVar:
27
+ Enabled: false
28
+
29
+ RSpec/ExampleLength:
30
+ Max: 10
31
+
32
+ Style/FrozenStringLiteralComment:
33
+ Enabled: false
34
+
35
+ Lint/EmptyFile:
36
+ Enabled: false
37
+
38
+ RSpec/BeNil:
39
+ Enabled: false
40
+
41
+ Style/RaiseArgs:
42
+ Enabled: false
43
+
44
+ Style/WhileUntilModifier:
45
+ Enabled: false
46
+
47
+ require:
48
+ - rubocop-rake
49
+ - rubocop-rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-10-03
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 milkeclair
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ ## JpQuest
2
+
3
+ Translator for `.snbt` files. (title, subtitle, description)
4
+ If you want to translate to other languages than Japanese,
5
+ please add the `exchange_language` option during initialization.
6
+ Can always get help with `JpQuest.help`
7
+
8
+ #### Example
9
+
10
+ `JpQuest::Performer.new(exchange_language: "English")`
11
+
12
+ ## Steps
13
+
14
+ 1. Download [release](https://github.com/milkeclair/jp_quest/releases)
15
+ 2. Make `.env` file
16
+ 3. Add `OPENAI_API_KEY=your_api_key` to `.env`
17
+ 4. Optional: Add `OPENAI_MODEL=some_openai_model` to `.env` **(default: gpt-4o-mini)**
18
+ 5. Add `some.snbt` to `quests` directory
19
+ 6. Check `output` directory
20
+
21
+ ## Initialize Options
22
+
23
+ #### output_logs
24
+
25
+ Want to output OpenAI usage logs?
26
+ **(default: true)**
27
+
28
+ #### except_words
29
+
30
+ Words that you don't want to translate
31
+ **(default: empty array)**
32
+
33
+ #### exchange_language
34
+
35
+ Which language do you want to translate to?
36
+ **(default: japanese)**
37
+
38
+ #### display_help
39
+
40
+ Want to display help?
41
+ **(default: true)**
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+ require "yard"
7
+ require_relative "rake_helper"
8
+
9
+ desc "analysis"
10
+ task :analysis do
11
+ sh "bundle install"
12
+
13
+ RakeHelper.init_rake_tasks
14
+
15
+ puts "--- rspec ---"
16
+ Rake::Task[:spec].invoke
17
+
18
+ puts "--- rubocop ---"
19
+ Rake::Task[:rubocop].invoke
20
+
21
+ puts "--- yard ---"
22
+ Rake::Task[:yard].invoke
23
+ end
24
+
25
+ desc "push to github packages and rubygems"
26
+ task :push do
27
+ sh "bundle install"
28
+
29
+ puts "--- build ---"
30
+ RakeHelper.build_gem
31
+
32
+ puts "--- push to github packages ---"
33
+ RakeHelper.push_to_github_packages
34
+
35
+ puts "--- push to rubygems ---"
36
+ RakeHelper.push_to_rubygems
37
+ end
38
+
39
+ task default: :analysis
data/dist/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "jp_quest", "~> 0.1.0"
data/dist/example.rb ADDED
@@ -0,0 +1,8 @@
1
+ require_relative "../lib/jp_quest"
2
+
3
+ performer = JpQuest::Performer.new
4
+
5
+ # file_path = "some.snbt"
6
+ # performer.perform(file_path)
7
+
8
+ performer.perform_directory
data/dist/start.bat ADDED
@@ -0,0 +1,4 @@
1
+ call gem install bundler
2
+ call bundle install
3
+ call bundle exec ruby example.rb
4
+ pause
@@ -0,0 +1,35 @@
1
+ module JpQuest
2
+ # 基底のエラークラス
3
+ class Error < StandardError; end
4
+
5
+ # パスが見つからない場合のエラークラス
6
+ class PathNotFoundError < Error
7
+ # ヒアドキュメントを使うと、先頭と末尾に空行が入る
8
+ # エラーメッセージも変更しやすいのでこうしている
9
+ DOES_NOT_EXIST = <<~TEXT.freeze
10
+ \n
11
+ Path does not exist: %s
12
+ TEXT
13
+
14
+ # @param [String] path パス
15
+ # @return [JpQuest::PathNotFoundError]
16
+ def initialize(path)
17
+ super(format(DOES_NOT_EXIST, path))
18
+ end
19
+ end
20
+
21
+ # 行数が異なる場合のエラークラス
22
+ class InvalidLineCountError < Error
23
+ INVALID_LINE_COUNT = <<~TEXT.freeze
24
+ \n
25
+ Invalid line count: %s
26
+ TEXT
27
+
28
+ # @param [Integer] expect_count 期待する行数
29
+ # @param [Integer] actual_count 実際の行数
30
+ # @return [JpQuest::InvalidLineCountError]
31
+ def initialize(expect_count, actual_count)
32
+ super(format(INVALID_LINE_COUNT, "Expected: #{expect_count}, Actual: #{actual_count}"))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,112 @@
1
+ module JpQuest
2
+ # SNBT形式のファイルからdescription: ["some description"]を抽出するモジュール
3
+ module DescriptionExtractor
4
+ # description: ["some description"]を抽出する
5
+ #
6
+ # @param [String] file_path ファイルのパス
7
+ # @return [Array<Hash>] 説明、開始行番号、終了行番号の配列
8
+ def extract_descriptions(file_path)
9
+ lines = File.readlines(file_path)
10
+ extract_from_file(lines)
11
+ end
12
+
13
+ private
14
+
15
+ # ファイルから説明を抽出する
16
+ #
17
+ # @param [Array<String>] lines 行の配列
18
+ # @return [Array<Hash>] 説明、開始行番号、終了行番号、インデントのハッシュの配列
19
+ def extract_from_file(lines)
20
+ descs = []
21
+ desc_content = []
22
+ start_line = nil
23
+
24
+ lines.each_with_index do |line, index|
25
+ indent = count_indent(line)
26
+
27
+ # 1行の説明の場合はそのままハッシュに変換
28
+ # 複数行の場合は、開始行と終了行の間の説明を抽出する
29
+ if oneline?(line)
30
+ descs << build_oneline(line, index, indent)
31
+ elsif start_line?(line)
32
+ start_line = index
33
+ elsif middle_line?(line, start_line)
34
+ desc_content << line.strip
35
+ elsif end_line?(line, start_line)
36
+ descs << build_multiline(desc_content, start_line, index, indent)
37
+ start_line = nil
38
+ desc_content = []
39
+ end
40
+ end
41
+
42
+ descs
43
+ end
44
+
45
+ # 1行かどうか
46
+ #
47
+ # @param [String] line 行
48
+ # @return [Boolean]
49
+ def oneline?(line)
50
+ oneline_description?(line)
51
+ end
52
+
53
+ # 開始行かどうか
54
+ #
55
+ # @param [String] line 行
56
+ # @return [Boolean]
57
+ def start_line?(line)
58
+ start_of?(line, key: :description)
59
+ end
60
+
61
+ # 終了行かどうか
62
+ #
63
+ # @param [String] line 行
64
+ # @param [Integer] start_line 開始行番号
65
+ # @return [Boolean]
66
+ def end_line?(line, start_line)
67
+ line.strip.end_with?("]") && start_line
68
+ end
69
+
70
+ # 中間行かどうか
71
+ #
72
+ # @param [String] line 行
73
+ # @param [Integer] start_line 開始行番号
74
+ # @return [Boolean]
75
+ def middle_line?(line, start_line)
76
+ line.strip != "]" && start_line
77
+ end
78
+
79
+ # 1行の処理
80
+ #
81
+ # @param [String] line 行
82
+ # @param [Integer] index 行番号
83
+ # @param [Integer] indent インデント
84
+ # @return [Hash] 説明、開始行番号、終了行番号、インデントのハッシュ
85
+ def build_oneline(line, index, indent)
86
+ {
87
+ type: :description,
88
+ text: extract_oneline(line, is_desc: true),
89
+ start_line: index,
90
+ end_line: index,
91
+ indent: indent
92
+ }
93
+ end
94
+
95
+ # 複数行の処理
96
+ #
97
+ # @param [Array<String>] content 説明の配列
98
+ # @param [Integer] start_line 開始行番号
99
+ # @param [Integer] index 行番号
100
+ # @param [Integer] indent インデント
101
+ # @return [Hash] 説明、開始行番号、終了行番号、インデントのハッシュ
102
+ def build_multiline(content, start_line, index, indent)
103
+ {
104
+ type: :description,
105
+ text: content.join("\n"),
106
+ start_line: start_line,
107
+ end_line: index,
108
+ indent: indent
109
+ }
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,26 @@
1
+ module JpQuest
2
+ # SNBT形式のファイルからsubtitle: "some subtitle"を抽出するモジュール
3
+ module SubtitleExtractor
4
+ # subtitle: "some subtitle"を抽出する
5
+ #
6
+ # @param [String] file_path ファイルのパス
7
+ # @return [Array<Hash>] サブタイトルと行番号の配列
8
+ def extract_subtitles(file_path)
9
+ subtitles = []
10
+ lines = File.readlines(file_path)
11
+ lines.each_with_index do |line, index|
12
+ next unless start_of?(line, key: :subtitle)
13
+
14
+ subtitles << {
15
+ type: :subtitle,
16
+ text: extract_oneline(line),
17
+ start_line: index,
18
+ end_line: index,
19
+ indent: count_indent(line)
20
+ }
21
+ end
22
+
23
+ subtitles
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module JpQuest
2
+ # SNBT形式のファイルからtitle: "some title"を抽出するモジュール
3
+ module TitleExtractor
4
+ # title: "some title"を抽出する
5
+ #
6
+ # @param [String] file_path ファイルのパス
7
+ # @return [Array<Hash>] タイトルと行番号の配列
8
+ def extract_titles(file_path)
9
+ titles = []
10
+ lines = File.readlines(file_path)
11
+ lines.each_with_index do |line, index|
12
+ next unless start_of?(line, key: :title)
13
+
14
+ titles << {
15
+ type: :title,
16
+ text: extract_oneline(line),
17
+ start_line: index,
18
+ end_line: index,
19
+ indent: count_indent(line)
20
+ }
21
+ end
22
+
23
+ titles
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,159 @@
1
+ require_relative "indent_helper"
2
+
3
+ module JpQuest
4
+ # 翻訳された内容をSNBT形式に整形するクラス
5
+ class Formatter
6
+ include IndentHelper
7
+
8
+ # 保存できるように整形
9
+ #
10
+ # @param [Hash] content コンテンツ
11
+ # @param [String] indent インデント
12
+ # @return [String] SNBT形式に整形したコンテンツ
13
+ def format_overwritable_lines(content, indent)
14
+ mid_indent = middle_indent(content[:indent])
15
+ indented_lines = add_indent_for_middle_lines(content, mid_indent)
16
+ formatted_lines = add_missing_lines(indented_lines, content, mid_indent)
17
+ format_for_snbt(formatted_lines, indent, content[:type])
18
+ end
19
+
20
+ # SNBT形式に整形
21
+ #
22
+ # @param [Array<String>] lines 行
23
+ # @param [String] indent インデント
24
+ # @param [Symbol] type コンテンツの種類
25
+ # @return [String] SNBT形式に整形した行
26
+ def format_for_snbt(lines, indent, type)
27
+ lines.map! { |line| delete_quotes(line) }
28
+ lines.map!(&:strip) unless type == :description
29
+
30
+ formatted_lines =
31
+ if lines.length == 1
32
+ type == :description ? "[#{lines[0].strip}]" : lines[0].strip.to_s
33
+ else
34
+ # description: [
35
+ # "Hello"
36
+ # "World"
37
+ # ]
38
+ "[\n#{lines.join("\n")}\n#{indent}]"
39
+ end
40
+
41
+ # " description: ["hoge"]"のような形式にする
42
+ "#{indent}#{type}: #{formatted_lines}"
43
+ end
44
+
45
+ # 不足している行を追加
46
+ #
47
+ # @param [Array<String>] lines 行
48
+ # @param [Hash] content コンテンツ
49
+ # @param [String] middle_indent 中間行のインデント
50
+ # @return [void]
51
+ def add_missing_lines(lines, content, middle_indent)
52
+ required_lines = extract_required_line_counts(content)
53
+
54
+ while lines.length < required_lines
55
+ lines << empty_middle_line(middle_indent)
56
+ end
57
+
58
+ lines
59
+ end
60
+
61
+ # 必要な行数を抽出
62
+ #
63
+ # @param [Hash] content コンテンツ
64
+ # @return [Integer] 必要な行数
65
+ def extract_required_line_counts(content)
66
+ # start_lineが1、end_lineが5の場合、必要な行数はブラケットを抜いて3行
67
+ # そのため、(end(5) - start(1)) + 1行 - ブラケット2行 = 3行となる
68
+ line_offset, without_brackets = 1, 2
69
+
70
+ (content[:end_line] - content[:start_line]) + line_offset - without_brackets
71
+ end
72
+
73
+ # 中間行のインデントを追加
74
+ #
75
+ # @param [Hash] content コンテンツ
76
+ # @param [String] middle_indent 中間行のインデント
77
+ # @return [Array<String>] 中間行のインデントを追加した行
78
+ def add_indent_for_middle_lines(content, middle_indent)
79
+ content[:text].split("\n").map do |line|
80
+ "#{middle_indent}\"#{line}\""
81
+ end
82
+ end
83
+
84
+ # 中間行の空行を作成
85
+ #
86
+ # @param [String] middle_indent 中間行のインデント
87
+ # @return [String] 空行
88
+ def empty_middle_line(middle_indent)
89
+ "#{middle_indent}\"\""
90
+ end
91
+
92
+ # 不要な引用符を削除
93
+ #
94
+ # @param [String] line 行
95
+ # @return [String] 不要な引用符を削除した行
96
+ def delete_quotes(line)
97
+ line = delete_dup_quotes(line)
98
+ line = delete_jp_quotes(line)
99
+ delete_curved_quotes(line)
100
+ end
101
+
102
+ private
103
+
104
+ # 不要なダブルクオートを削除
105
+ #
106
+ # @param [String] line 行
107
+ # @return [String] 不要なダブルクオートを削除した行
108
+ def delete_dup_quotes(line)
109
+ # ""Hello""、""""
110
+ deletable_regs = [/"{2,}.+".*"/, /"{3,}/]
111
+ return line unless deletable_regs.any? { |reg| line.match?(reg) }
112
+
113
+ # ""
114
+ dup_reg = /"{2,}/
115
+ # """"に一致する場合は空白行なので、""に変換する
116
+ if line.strip.match?(deletable_regs[1])
117
+ line.gsub(dup_reg, '""')
118
+ else
119
+ # 行間にある余計なダブルクオートを削除するため、一度全てのダブルクオートを削除している
120
+ line = line.gsub('"', "")
121
+ # インデントの調整
122
+ indent_count = normalize_indent(line[/^\s*/].length)
123
+ line_start = /^(\s*)/
124
+ # 行頭にインデントとダブルクオートを追加
125
+ line = line.sub(line_start, "#{" " * indent_count}\"")
126
+ # 行末のダブルクオートを追加
127
+ "#{line}\""
128
+ end
129
+ end
130
+
131
+ # 不要な鍵括弧を削除
132
+ #
133
+ # @param [String] line 行
134
+ # @return [String] 不要な鍵括弧を削除した行
135
+ def delete_jp_quotes(line)
136
+ # 「Hello」
137
+ deletable_reg = /「.*」/
138
+ return line unless line.match?(deletable_reg)
139
+
140
+ jp_quotes = [/「/, /」/]
141
+ jp_quotes.each { |quo| line = line.gsub(quo, "") }
142
+ line
143
+ end
144
+
145
+ # 不要な曲がった引用符を削除
146
+ #
147
+ # @param [String] line 行
148
+ # @return [String] 不要な曲がった引用符を削除した行
149
+ def delete_curved_quotes(line)
150
+ # “Hello”
151
+ deletable_reg = /“.*”/
152
+ return line unless line.match?(deletable_reg)
153
+
154
+ curved_quotes = [/“/, /”/]
155
+ curved_quotes.each { |quo| line = line.gsub(quo, "") }
156
+ line
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,130 @@
1
+ require "rainbow"
2
+
3
+ module JpQuest
4
+ # JpQuest gemについてのヘルプを表示するクラス
5
+ class Help
6
+ # JpQuest gemについてのヘルプを表示する
7
+ #
8
+ # @return [void]
9
+ def self.help
10
+ puts <<~HELP
11
+ #{cyan("=== JpQuest Help ===================================================================").bold}\n
12
+ #{help_warning}
13
+ #{help_intro}
14
+ #{help_steps}
15
+ #{help_init_options}
16
+ #{help_information}
17
+ #{cyan("====================================================================================").bold}
18
+ HELP
19
+ end
20
+
21
+ # 警告
22
+ #
23
+ # @return [String]
24
+ def self.help_warning
25
+ <<~WARNING
26
+ #{red("Warning:").bold} Please be careful your OpenAI API usage cost.
27
+ WARNING
28
+ end
29
+
30
+ # gemの説明
31
+ #
32
+ # @return [String]
33
+ def self.help_intro
34
+ <<~INTRO
35
+ #{cyan("Introduction:").bold}
36
+ Translator for #{green(".snbt")} files.
37
+ If you want to translate to other languages than Japanese,
38
+ please add the #{green("exchange_language")} option during initialization.
39
+ Example: #{green("JpQuest::Performer.new(exchange_language: \"English\")")}
40
+ INTRO
41
+ end
42
+
43
+ # 使用手順
44
+ #
45
+ # @return [String]
46
+ def self.help_steps
47
+ <<~STEPS
48
+ #{cyan("Steps:").bold}
49
+ 1. exec #{green("touch .env")} bash command
50
+ 2. Add #{green("OPENAI_API_KEY=your_api_key")} to .env
51
+ 3. Optional: Add #{green("OPENAI_MODEL=some_openai_model")} to .env (default: gpt-4o-mini)
52
+ 4. Add "quests" directory to your project
53
+ 5. #{green("gem install jp_quest")} or #{green("gem \"jp_quest\"")}
54
+ 6. Add #{green("require \"jp_quest\"")}
55
+ 7. Add #{green("jp_quest = JpQuest::Performer.new")}
56
+ 8. #{green("jp_quest.perform(\"file_path\")")} or
57
+ #{green("jp_quest.perform_directory(dir_path: \"dir_path\")")}
58
+ 9. Check "output" directory
59
+ STEPS
60
+ end
61
+
62
+ # newメソッドのオプション
63
+ #
64
+ # @return [String]
65
+ def self.help_init_options
66
+ <<~OPTIONS
67
+ #{cyan("Initialize Options:").bold}
68
+ output_logs:
69
+ Want to output OpenAI usage logs?
70
+ (default: true)
71
+ except_words:
72
+ Words that you don't want to translate
73
+ (default: empty array)
74
+ exchange_language:
75
+ Which language do you want to translate to?
76
+ (default: japanese)
77
+ display_help:
78
+ Want to display help?
79
+ (default: true)
80
+ OPTIONS
81
+ end
82
+
83
+ # その他gemに関する情報
84
+ #
85
+ # @return [String]
86
+ def self.help_information
87
+ <<~INFORMATION
88
+ #{cyan("Information:").bold}
89
+ jp_quest:
90
+ #{link("https://github.com/milkeclair/jp_quest")}
91
+ current version: #{JpQuest::VERSION}
92
+ translator:
93
+ #{link("https://github.com/milkeclair/jp_translator_from_gpt")}
94
+ current version: #{JpTranslatorFromGpt::VERSION}
95
+ INFORMATION
96
+ end
97
+
98
+ # 出力を青色にする
99
+ #
100
+ # @param [String] str
101
+ # @return [String]
102
+ def self.cyan(str)
103
+ Rainbow(str).cyan
104
+ end
105
+
106
+ # 出力を緑色にする
107
+ #
108
+ # @param [String] str
109
+ # @return [String]
110
+ def self.green(str)
111
+ Rainbow(str).green
112
+ end
113
+
114
+ # 出力を赤色にする
115
+ #
116
+ # @param [String] str
117
+ # @return [String]
118
+ def self.red(str)
119
+ Rainbow(str).red
120
+ end
121
+
122
+ # 出力をリンクのスタイルにする
123
+ #
124
+ # @param [String] str
125
+ # @return [String]
126
+ def self.link(str)
127
+ Rainbow(str).underline.bright
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,36 @@
1
+ module JpQuest
2
+ # インデントを扱うモジュール
3
+ module IndentHelper
4
+ # インデントを作成
5
+ #
6
+ # @param [Integer] indent インデント数
7
+ # @return [String] インデント
8
+ def create_indent(indent)
9
+ " " * indent
10
+ end
11
+
12
+ # 中間行のインデントを作成
13
+ #
14
+ # @param [Integer] indent インデント数
15
+ # @return [String] 中間行のインデント
16
+ def middle_indent(indent)
17
+ " " * (indent + 1)
18
+ end
19
+
20
+ # インデントを調整
21
+ #
22
+ # @param [Integer] indent インデント数
23
+ # @return [Integer] 調整後のインデント数
24
+ def normalize_indent(indent)
25
+ dup_indent = 12
26
+ if indent > dup_indent
27
+ half = 2
28
+ half_indent = indent / half
29
+ # インデントの数は偶数にする
30
+ half_indent.even? ? half_indent : half_indent + 1
31
+ else
32
+ indent
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ require "jp_translator_from_gpt"
2
+ require_relative "help"
3
+ require_relative "error"
4
+ require_relative "reader"
5
+ require_relative "writer"
6
+
7
+ module JpQuest
8
+ # 翻訳を実行するクラス
9
+ # JpTranslatorFromGptを使用して翻訳を行う
10
+ class Performer
11
+ # @param [Boolean] output_logs ログを出力するか
12
+ # @param [Array<String>] except_words 翻訳しない単語
13
+ # @param [String] exchange_language どの言語に翻訳するか
14
+ # @param [Boolean] display_help ヘルプを表示するか
15
+ # @return [JpQuest::Performer]
16
+ def initialize(output_logs: true, except_words: [], exchange_language: "japanese", display_help: true)
17
+ @translator = JpTranslatorFromGpt::Translator.new(
18
+ output_logs: output_logs,
19
+ except_words: except_words,
20
+ exchange_language: exchange_language
21
+ )
22
+ @reader = nil
23
+ @writer = nil
24
+ @progress_bar = nil
25
+
26
+ JpQuest.help if display_help
27
+ end
28
+
29
+ # ファイルを翻訳して出力する
30
+ #
31
+ # @param [String] file_path ファイルのパス
32
+ # @return [void]
33
+ def perform(file_path)
34
+ file_path = File.expand_path(file_path)
35
+ validate_path(file_path)
36
+
37
+ @reader, @writer = JpQuest::Reader.new(file_path), JpQuest::Writer.new(file_path)
38
+ results = @reader.extract_all.flatten
39
+ @progress_bar = JpQuest.create_progress_bar(file_path, results.length)
40
+
41
+ results.each do |result|
42
+ result[:text] = @translator.translate(result[:text])
43
+ @writer.overwrites(result)
44
+ @progress_bar.increment
45
+ end
46
+
47
+ puts "Completed!"
48
+ end
49
+
50
+ # ディレクトリ内のファイルを翻訳して出力する
51
+ #
52
+ # @param [String] dir_path ディレクトリのパス
53
+ # @return [void]
54
+ def perform_directory(dir_path: "quests")
55
+ dir_path = File.expand_path(dir_path)
56
+ validate_path(dir_path)
57
+
58
+ # **でサブディレクトリも含めて取得
59
+ Dir.glob("#{dir_path}/**.snbt").each do |file_path|
60
+ perform(file_path)
61
+ end
62
+ end
63
+
64
+ # ファイルの存在を確認する
65
+ # 存在しない場合はPathNotFoundErrorを投げる
66
+ #
67
+ # @param [String] path ファイルのパス
68
+ # @return [void]
69
+ def validate_path(path)
70
+ path = File.expand_path(path)
71
+ raise JpQuest::PathNotFoundError.new(path) unless File.exist?(path)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "extractor/title"
4
+ require_relative "extractor/subtitle"
5
+ require_relative "extractor/description"
6
+
7
+ module JpQuest
8
+ # SNBT形式のファイルからタイトル、サブタイトル、説明を抽出するクラス
9
+ class Reader
10
+ include TitleExtractor
11
+ include SubtitleExtractor
12
+ include DescriptionExtractor
13
+
14
+ DESC_START_LENGTH = 14
15
+ DESC_END_LENGTH = -2
16
+
17
+ # @param [String] file_path ファイルのパス
18
+ # @return [JpQuest::Reader]
19
+ def initialize(file_path)
20
+ @file_path = file_path
21
+ end
22
+
23
+ # タイトル、サブタイトル、説明を抽出する
24
+ #
25
+ # @return [Array<Array<Hash>>] タイトル、サブタイトル、説明の配列
26
+ def extract_all
27
+ titles = extract_titles
28
+ subtitles = extract_subtitles
29
+ descriptions = extract_descriptions
30
+
31
+ [titles, subtitles, descriptions]
32
+ end
33
+
34
+ # タイトルを抽出する
35
+ #
36
+ # @return [Array<Hash>] タイトル、行番号、インデントの配列
37
+ def extract_titles
38
+ super(@file_path)
39
+ end
40
+
41
+ # サブタイトルを抽出する
42
+ #
43
+ # @return [Array<Hash>] サブタイトル、行番号、インデントの配列
44
+ def extract_subtitles
45
+ super(@file_path)
46
+ end
47
+
48
+ # 説明を抽出する
49
+ #
50
+ # @return [Array<Hash>] 説明、開始行、終了行、インデントの配列
51
+ def extract_descriptions
52
+ super(@file_path)
53
+ end
54
+
55
+ # インデントを数える
56
+ #
57
+ # @param [String] unstripped_line stripされていない行
58
+ # @return [Integer] インデントの数
59
+ def count_indent(unstripped_line)
60
+ unstripped_line.length - unstripped_line.lstrip.length
61
+ end
62
+
63
+ # title: "some title"のような形式から、"some title"を抽出する
64
+ # 説明の場合は、description: ["some description"]のような形式から、"some description"を抽出する
65
+ #
66
+ # @param [String] line 行
67
+ # @param [Boolean] is_desc 説明かどうか
68
+ # @return [String] タイトル or サブタイトル or 説明
69
+ def extract_oneline(line, is_desc: false)
70
+ stripped_line = line.strip
71
+ return stripped_line.split(":", 2)[1] unless is_desc
72
+
73
+ if oneline_description?(line)
74
+ stripped_line[DESC_START_LENGTH..DESC_END_LENGTH]
75
+ elsif start_of?(line, key: :description)
76
+ stripped_line.split("[", 2)[1]
77
+ else
78
+ stripped_line.split("]", 2)[0]
79
+ end
80
+ end
81
+
82
+ # 1行の説明かどうか
83
+ #
84
+ # @param [String] line 行
85
+ # @return [Boolean]
86
+ def oneline_description?(line)
87
+ stripped_line = line.strip
88
+ start_of?(line, key: :description) && stripped_line.end_with?("]")
89
+ end
90
+
91
+ # どのコンテンツの開始行か
92
+ #
93
+ # @param [String] line 行
94
+ # @param [Symbol] key コンテンツの種類
95
+ # @return [Boolean]
96
+ def start_of?(line, key:)
97
+ stripped_line = line.strip
98
+ sections = {
99
+ title: "title:",
100
+ subtitle: "subtitle:",
101
+ description: "description: ["
102
+ }
103
+
104
+ stripped_line.start_with?(sections[key])
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JpQuest
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,71 @@
1
+ require_relative "error"
2
+ require_relative "formatter"
3
+ require_relative "indent_helper"
4
+
5
+ module JpQuest
6
+ # 翻訳された内容を整形して出力するクラス
7
+ class Writer
8
+ include IndentHelper
9
+
10
+ # @param [String] file_path ファイルのパス
11
+ # @return [JpQuest::Writer]
12
+ def initialize(file_path)
13
+ @input_file_path = file_path
14
+ @output_file_path = file_path.gsub("quests", "output/quests")
15
+ @formatter = JpQuest::Formatter.new
16
+ end
17
+
18
+ # 翻訳された内容でoutput_file_pathを上書きする
19
+ #
20
+ # @param [Hash] translated_contents 翻訳された内容
21
+ # @return [void]
22
+ def overwrites(translated_contents)
23
+ # ディレクトリが存在しない場合は作成
24
+ FileUtils.mkdir_p(File.dirname(@output_file_path))
25
+
26
+ # 一度上書きしている場合は上書き後のファイルを読み込む
27
+ # 常に上書き前のファイルを読み込むと、前回の上書きが消えてしまう
28
+ lines = File.readlines(first_overwrite? ? @input_file_path : @output_file_path)
29
+ formatted_lines = overwrite_lines(lines, translated_contents)
30
+
31
+ File.open(@output_file_path, "w") do |file|
32
+ file.puts formatted_lines
33
+ end
34
+
35
+ handle_line_count_error(@output_file_path, lines.length)
36
+ end
37
+
38
+ private
39
+
40
+ # 行を上書きする
41
+ #
42
+ # @param [Array<String>] lines 行
43
+ # @param [Hash] content コンテンツ
44
+ # @return [Array<String>] 上書きされた行
45
+ def overwrite_lines(lines, content)
46
+ indent = create_indent(content[:indent])
47
+ overwritable_lines = @formatter.format_overwritable_lines(content, indent)
48
+ lines[content[:start_line]..content[:end_line]] = overwritable_lines.split("\n")
49
+ lines
50
+ end
51
+
52
+ # 最初の上書きかどうか
53
+ #
54
+ # @return [Boolean] 最初の上書きかどうか
55
+ def first_overwrite?
56
+ !File.exist?(@output_file_path)
57
+ end
58
+
59
+ # 翻訳前と翻訳後の行数が異なる場合はエラーを発生させる
60
+ #
61
+ # @param [String] output_file_path 出力ファイルのパス
62
+ # @param [Integer] before_line_count 翻訳前の行数
63
+ # @return [void]
64
+ def handle_line_count_error(output_file_path, before_line_count)
65
+ after_line_count = File.readlines(output_file_path).length
66
+ return if before_line_count == after_line_count
67
+
68
+ raise JpQuest::InvalidLineCountError.new(before_line_count, after_line_count)
69
+ end
70
+ end
71
+ end
data/lib/jp_quest.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-progressbar"
4
+ require_relative "jp_quest/version"
5
+ require_relative "jp_quest/help"
6
+ require_relative "jp_quest/performer"
7
+
8
+ # SNBT形式のファイルを翻訳する
9
+ # 翻訳できるプロパティ
10
+ # - title
11
+ # - subtitle
12
+ # - description
13
+ module JpQuest
14
+ # JpQuest gemについてのヘルプを表示する
15
+ #
16
+ # @return [void]
17
+ def self.help
18
+ JpQuest::Help.help
19
+ end
20
+
21
+ # プログレスバーを生成する
22
+ #
23
+ # @param [String] file_path ファイルのパス
24
+ # @param [Integer] total プログレスバーの合計数
25
+ # @return [ProgressBar::Base] プログレスバー
26
+ def self.create_progress_bar(file_path, total)
27
+ # パスの内、カレントディレクトリ配下のパス以外は邪魔なので削除
28
+ # 例: /Users/user/quests/some.snbt -> /quests/some.snbt
29
+ puts "\nFile path: #{file_path.gsub(Dir.pwd, "")}"
30
+
31
+ ProgressBar.create(
32
+ title: "Translating...",
33
+ total: total,
34
+ progress_mark: "#",
35
+ format: "%t [%B]",
36
+ length: 80,
37
+ projector: {
38
+ type: "smoothed",
39
+ strength: 0.1
40
+ }
41
+ )
42
+ end
43
+ end
data/rake_helper.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RakeHelper
4
+ GITHUB_PACKAGES_PUSH_COMMAND =
5
+ "gem push --key github --host https://rubygems.pkg.github.com/milkeclair " \
6
+ "pkg/jp_quest-#{JpQuest::VERSION}.gem".freeze
7
+
8
+ RUBYGEMS_PUSH_COMMAND =
9
+ "gem push --host https://rubygems.org " \
10
+ "pkg/jp_quest-#{JpQuest::VERSION}.gem".freeze
11
+
12
+ def self.init_rake_tasks
13
+ RSpec::Core::RakeTask.new(:spec) { |task| task.verbose = false }
14
+ RuboCop::RakeTask.new
15
+ YARD::Rake::YardocTask.new
16
+ end
17
+
18
+ def self.build_gem
19
+ abort("gemのビルドに失敗しました") unless system("rake build")
20
+ end
21
+
22
+ def self.push_to_github_packages
23
+ abort("githubへのgemのpushに失敗しました") unless system(GITHUB_PACKAGES_PUSH_COMMAND)
24
+ end
25
+
26
+ def self.push_to_rubygems
27
+ abort("rubygemsへのgemのpushに失敗しました") unless system(RUBYGEMS_PUSH_COMMAND)
28
+ end
29
+ end
data/sig/jp_quest.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module JpQuest
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jp_quest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - milkeclair
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jp_translator_from_gpt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.1.0
27
+ description: ftbquestを日本語化する
28
+ email:
29
+ - milkeclair.noreply@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".licensed.yml"
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - CHANGELOG.md
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - dist/Gemfile
42
+ - dist/example.rb
43
+ - dist/start.bat
44
+ - lib/jp_quest.rb
45
+ - lib/jp_quest/error.rb
46
+ - lib/jp_quest/extractor/description.rb
47
+ - lib/jp_quest/extractor/subtitle.rb
48
+ - lib/jp_quest/extractor/title.rb
49
+ - lib/jp_quest/formatter.rb
50
+ - lib/jp_quest/help.rb
51
+ - lib/jp_quest/indent_helper.rb
52
+ - lib/jp_quest/performer.rb
53
+ - lib/jp_quest/reader.rb
54
+ - lib/jp_quest/version.rb
55
+ - lib/jp_quest/writer.rb
56
+ - rake_helper.rb
57
+ - sig/jp_quest.rbs
58
+ homepage: https://github.com/milkeclair/jp_quest
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/milkeclair/jp_quest
63
+ source_code_uri: https://github.com/milkeclair/jp_quest/blob/main
64
+ changelog_uri: https://github.com/milkeclair/jp_quest/blob/main/CHANGELOG.md
65
+ rubygems_mfa_required: 'true'
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.0.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.21
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: translate to japanese for ftbquest
85
+ test_files: []