jp_quest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []