jekyll-chatgpt-translate 0.0.12 → 0.0.14
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 +4 -4
- data/.gitignore +2 -1
- data/features/cli.feature +7 -6
- data/features/step_definitions/steps.rb +2 -1
- data/jekyll-chatgpt-translate.gemspec +1 -1
- data/lib/jekyll-chatgpt-translate/chatgpt.rb +7 -12
- data/lib/jekyll-chatgpt-translate/generator.rb +13 -9
- data/lib/jekyll-chatgpt-translate/ping.rb +18 -13
- data/lib/jekyll-chatgpt-translate/plain.rb +16 -13
- data/lib/jekyll-chatgpt-translate/version.rb +1 -1
- data/test/test_generator.rb +119 -1
- data/test/test_ping.rb +9 -0
- data/test/test_plain.rb +29 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b8a34c60ab2378b8dc26ab4b189ff3fa168cbb864c80db92a854d68b58def59
|
4
|
+
data.tar.gz: 3654fe32f201e8a155cd5f154129c24350333b6f2459cd86bb511c0acc22c0c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea02f9faba8ffedaa3e25bd428bde6aaba7a7a484bda745cc270a9f97f1a40211b776cff7d5a8c5d297854f107ba311b10dd5885bb6eff8de353f4a8aa9778d6
|
7
|
+
data.tar.gz: 6f3cc93df6fff8290f3a6d45bc980a9c5b273154a98ab7c18ddff1571c55dd8c2796e9ef965c3a0df72ff175c42ed54b72f4b4ecb5d2d8bb21864ae3b4aa0c7c
|
data/.gitignore
CHANGED
data/features/cli.feature
CHANGED
@@ -40,11 +40,12 @@ Feature: Simple site building
|
|
40
40
|
Hello, world!
|
41
41
|
"""
|
42
42
|
Then I build Jekyll site
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
And File "_chatgpt-translated/cn/2023-01-01-hello-cn.md" exists
|
44
|
+
And File "_chatgpt-translated/cn/2023-01-01-hello-cn.md" contains "/2023-01-01-hello-chinese.html"
|
45
|
+
And File "_site/2023/01/01/hello.html" exists
|
46
|
+
And File "_site/2023/01/01/hello.html" contains "The Chinese: /2023-01-01-hello-chinese.html"
|
47
|
+
And File "_site/2023-01-01-hello-chinese.html" exists
|
48
|
+
And File "_site/2023-01-01-hello-chinese.html" contains "The original: /2023/01/01/hello.html"
|
49
|
+
And File "_site/2023/hello-french.html" exists
|
49
50
|
And Exit code is zero
|
50
51
|
|
@@ -50,10 +50,11 @@ Then(/^Stdout contains "([^"]*)"$/) do |txt|
|
|
50
50
|
end
|
51
51
|
|
52
52
|
Then(/^File "([^"]*)" exists$/) do |name|
|
53
|
-
raise "The file \"#{name}\" is absent:\n#{`tree`}" unless File.exist?(name)
|
53
|
+
raise "The file \"#{name}\" is absent:\n#{`tree -s`}" unless File.exist?(name)
|
54
54
|
end
|
55
55
|
|
56
56
|
Then(/^File "([^"]*)" contains "([^"]*)"$/) do |name, text|
|
57
|
+
raise "The file \"#{name}\" is absent" unless File.exist?(name)
|
57
58
|
content = File.read(name)
|
58
59
|
raise "The file \"#{name}\" doesn't contain \"#{text}\":\n#{content}" unless content.include?(text)
|
59
60
|
end
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
29
29
|
s.required_ruby_version = '>= 2.6'
|
30
30
|
s.name = 'jekyll-chatgpt-translate'
|
31
|
-
s.version = '0.0.
|
31
|
+
s.version = '0.0.14'
|
32
32
|
s.license = 'MIT'
|
33
33
|
s.summary = 'Translate Jekyll Pages Through ChatGPT'
|
34
34
|
s.description = [
|
@@ -92,17 +92,12 @@ through #{@model} in #{(Time.now - start).round(2)}s")
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def prompt
|
95
|
-
|
96
|
-
'
|
97
|
-
|
98
|
-
'
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
ISO_639.find_by_code(@source),
|
103
|
-
'to',
|
104
|
-
ISO_639.find_by_code(@target)
|
105
|
-
].join(' ')
|
106
|
-
end
|
95
|
+
[
|
96
|
+
'Please, translate this paragraph from',
|
97
|
+
ISO_639.find_by_code(@source),
|
98
|
+
'to',
|
99
|
+
ISO_639.find_by_code(@target),
|
100
|
+
', don\'t change proper nouns'
|
101
|
+
].join(' ')
|
107
102
|
end
|
108
103
|
end
|
@@ -23,6 +23,7 @@
|
|
23
23
|
# SOFTWARE.
|
24
24
|
|
25
25
|
require 'jekyll'
|
26
|
+
require 'json'
|
26
27
|
require_relative 'chatgpt'
|
27
28
|
require_relative 'permalink'
|
28
29
|
require_relative 'ping'
|
@@ -60,13 +61,19 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
60
61
|
plain = GptTranslate::Plain.new(doc.content).to_s
|
61
62
|
config['targets'].each do |target|
|
62
63
|
link = GptTranslate::Permalink.new(doc, target['permalink']).to_path
|
63
|
-
next if GptTranslate::Ping.new(site, link).found?(doc.path, version)
|
64
64
|
lang = target['language']
|
65
65
|
raise 'Language must be defined for each target' if target.nil?
|
66
66
|
if total >= threshold
|
67
67
|
Jekyll.logger.info("Already generated #{total} pages, that's enough for today")
|
68
68
|
break
|
69
69
|
end
|
70
|
+
path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
|
71
|
+
FileUtils.mkdir_p(File.dirname(path))
|
72
|
+
File.write(path, '') # in order to surpress warnings in Page ctor
|
73
|
+
dest = Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path)).destination(site.dest)
|
74
|
+
doc.data["translated-#{lang}-url"] = link
|
75
|
+
doc.data['chatgpt-model'] = model
|
76
|
+
next if GptTranslate::Ping.new(site, link).found?(dest, version)
|
70
77
|
gpt = GptTranslate::ChatGPT.new(
|
71
78
|
key,
|
72
79
|
model,
|
@@ -74,17 +81,16 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
74
81
|
lang
|
75
82
|
)
|
76
83
|
translated = gpt.translate(plain)
|
77
|
-
path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
|
78
|
-
FileUtils.mkdir_p(File.dirname(path))
|
79
84
|
File.write(
|
80
85
|
path,
|
81
86
|
[
|
82
87
|
'---',
|
83
88
|
"layout: #{target['layout'] || layout}",
|
84
|
-
"title: #{doc
|
85
|
-
"
|
86
|
-
"
|
87
|
-
"
|
89
|
+
"title: #{doc['title'].to_json}",
|
90
|
+
"description: #{doc['description'].to_json}",
|
91
|
+
"permalink: #{link.to_json}",
|
92
|
+
"translated-original-url: #{doc.url.to_json}",
|
93
|
+
"chatgpt-model: #{model.to_json}",
|
88
94
|
'---',
|
89
95
|
'',
|
90
96
|
translated,
|
@@ -92,8 +98,6 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
92
98
|
"#{marker}\n{: .jekyll-chatgpt-translate}"
|
93
99
|
].join("\n")
|
94
100
|
)
|
95
|
-
doc.data["translated-#{lang}-url"] = link
|
96
|
-
doc.data['chatgpt-model'] = model
|
97
101
|
site.pages << Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path))
|
98
102
|
total += 1
|
99
103
|
Jekyll.logger.info("Translated via ChatGPT: #{path}")
|
@@ -51,21 +51,26 @@ class GptTranslate::Ping
|
|
51
51
|
home = @site.config['url']
|
52
52
|
return false if home.nil?
|
53
53
|
uri = Iri.new(home).path(@path)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
begin
|
55
|
+
before = Net::HTTP.get_response(URI(uri.to_s))
|
56
|
+
if before.is_a?(Net::HTTPSuccess)
|
57
|
+
html = before.body
|
58
|
+
if html.include?(marker)
|
59
|
+
Jekyll.logger.info("No need to translate, the page exists at \
|
60
|
+
\"#{uri}\" (#{html.split.count} words), saved to \"#{file}\"")
|
61
|
+
FileUtils.mkdir_p(File.dirname(file))
|
62
|
+
File.write(file, html)
|
63
|
+
return true
|
64
|
+
end
|
65
|
+
Jekyll.logger.info("Re-translation required for \"#{uri}\"")
|
66
|
+
else
|
67
|
+
Jekyll.logger.info("The page is absent, will translate \"#{uri}\"")
|
63
68
|
end
|
64
|
-
Jekyll.logger.
|
65
|
-
|
66
|
-
Jekyll.logger.
|
69
|
+
Jekyll.logger.debug("GET \"#{uri}\": #{before.code}")
|
70
|
+
rescue StandardError => e
|
71
|
+
Jekyll.logger.debug("Failed to ping \"#{uri}\": #{e.message}")
|
72
|
+
Jekyll.logger.info("The page is absent: \"#{uri}\"")
|
67
73
|
end
|
68
|
-
Jekyll.logger.debug("GET #{uri}: #{before.code}")
|
69
74
|
false
|
70
75
|
end
|
71
76
|
end
|
@@ -38,28 +38,31 @@ class GptTranslate::Plain
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def to_s
|
41
|
-
|
42
|
-
|
43
|
-
par.gsub!(
|
41
|
+
# To turn compact lists into proper lists
|
42
|
+
@markdown.gsub(/([^\n])\n(\s*\*)/, "\\1\n\n\\2").split(/\n{2,}/).compact.map do |par|
|
43
|
+
# par.gsub!("\n", ' ')
|
44
|
+
par.gsub!("\t", ' ')
|
45
|
+
par.gsub!(/ {2,}/, ' ')
|
44
46
|
# Liquid tags are removed, but this implementation is primitive
|
45
47
|
# Seehttps://stackoverflow.com/questions/
|
46
48
|
par.gsub!(/{{[^}]+}}/, '')
|
47
|
-
par.gsub!(/{
|
49
|
+
par.gsub!(/{%.+?%}/, '')
|
50
|
+
par.gsub!(/<!--.+?-->/m, '')
|
48
51
|
par.strip!
|
49
52
|
Redcarpet::Markdown.new(Strip).render(par)
|
50
53
|
end.join("\n\n").gsub(/\n{2,}/, "\n\n").strip
|
51
54
|
end
|
52
55
|
|
53
|
-
# To ignore/remove Liquid tags.
|
54
|
-
class NullDrop < Liquid::Drop
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
# # To ignore/remove Liquid tags.
|
57
|
+
# class NullDrop < Liquid::Drop
|
58
|
+
# def method_missing(*)
|
59
|
+
# nil
|
60
|
+
# end
|
58
61
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
62
|
+
# def respond_to_missing?(*)
|
63
|
+
# true
|
64
|
+
# end
|
65
|
+
# end
|
63
66
|
|
64
67
|
# Markdown to pain text.
|
65
68
|
class Strip < Redcarpet::Render::Base
|
data/test/test_generator.rb
CHANGED
@@ -23,6 +23,8 @@
|
|
23
23
|
# SOFTWARE.
|
24
24
|
|
25
25
|
require 'minitest/autorun'
|
26
|
+
require 'tmpdir'
|
27
|
+
require 'webmock/minitest'
|
26
28
|
require_relative '../lib/jekyll-chatgpt-translate/generator'
|
27
29
|
|
28
30
|
# Generator test.
|
@@ -30,7 +32,123 @@ require_relative '../lib/jekyll-chatgpt-translate/generator'
|
|
30
32
|
# Copyright:: Copyright (c) 2023 Yegor Bugayenko
|
31
33
|
# License:: MIT
|
32
34
|
class GptTranslate::GeneratorTest < Minitest::Test
|
35
|
+
class FakeSite
|
36
|
+
attr_reader :config
|
37
|
+
|
38
|
+
def initialize(config, doc)
|
39
|
+
@config = config
|
40
|
+
@doc = doc
|
41
|
+
end
|
42
|
+
|
43
|
+
def posts
|
44
|
+
FakePosts.new(@doc)
|
45
|
+
end
|
46
|
+
|
47
|
+
def pages
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
|
51
|
+
def permalink_style
|
52
|
+
''
|
53
|
+
end
|
54
|
+
|
55
|
+
def frontmatter_defaults
|
56
|
+
Jekyll::FrontmatterDefaults.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def converters
|
60
|
+
[Jekyll::Converters::Markdown.new({ 'markdown_ext' => 'md' })]
|
61
|
+
end
|
62
|
+
|
63
|
+
def source
|
64
|
+
''
|
65
|
+
end
|
66
|
+
|
67
|
+
def dest
|
68
|
+
''
|
69
|
+
end
|
70
|
+
|
71
|
+
def in_theme_dir(base, _foo = nil, _bar = nil)
|
72
|
+
base
|
73
|
+
end
|
74
|
+
|
75
|
+
def in_dest_dir(*paths)
|
76
|
+
paths[0].dup
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class FakeDocument
|
81
|
+
def initialize(path)
|
82
|
+
@path = path
|
83
|
+
end
|
84
|
+
|
85
|
+
def content
|
86
|
+
'Hello, world!'
|
87
|
+
end
|
88
|
+
|
89
|
+
def data
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
|
93
|
+
def []=(key, value); end
|
94
|
+
|
95
|
+
def [](key)
|
96
|
+
if key == 'date'
|
97
|
+
Time.now
|
98
|
+
elsif key == 'title'
|
99
|
+
'Hello!'
|
100
|
+
else
|
101
|
+
''
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def relative_path
|
106
|
+
@path
|
107
|
+
end
|
108
|
+
|
109
|
+
def url
|
110
|
+
'2023-01-01-hello.html'
|
111
|
+
end
|
112
|
+
|
113
|
+
def basename
|
114
|
+
'2023-01-01-hello.md'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class FakePosts
|
119
|
+
attr_reader :config
|
120
|
+
|
121
|
+
def initialize(doc)
|
122
|
+
@doc = doc
|
123
|
+
end
|
124
|
+
|
125
|
+
def docs
|
126
|
+
[FakeDocument.new(@doc)]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
33
130
|
def test_simple_scenario
|
34
|
-
|
131
|
+
Dir.mktmpdir do |home|
|
132
|
+
post = File.join(home, '2023-01-01-hello.md')
|
133
|
+
File.write(post, "---\ntitle: Hello\n---\n\nHello, world!")
|
134
|
+
site = FakeSite.new(
|
135
|
+
{
|
136
|
+
'url' => 'https://www.yegor256.com/',
|
137
|
+
'chatgpt-translate' => {
|
138
|
+
'targets' => [
|
139
|
+
{
|
140
|
+
'language' => 'cn',
|
141
|
+
'layout' => 'chinese',
|
142
|
+
'permalink' => ':slug.html'
|
143
|
+
}
|
144
|
+
]
|
145
|
+
}
|
146
|
+
},
|
147
|
+
FakeDocument.new({})
|
148
|
+
)
|
149
|
+
gen = GptTranslate::Generator.new
|
150
|
+
stub_request(:get, 'https://www.yegor256.com/.html').to_return(body: '')
|
151
|
+
gen.generate(site)
|
152
|
+
end
|
35
153
|
end
|
36
154
|
end
|
data/test/test_ping.rb
CHANGED
@@ -59,6 +59,15 @@ class GptTranslate::PingTest < Minitest::Test
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
def test_wrong_address
|
63
|
+
WebMock.allow_net_connect!
|
64
|
+
site = FakeSite.new({ 'url' => 'https://localhost:1/' })
|
65
|
+
ping = GptTranslate::Ping.new(site, '/boom.html')
|
66
|
+
Tempfile.open do |f|
|
67
|
+
assert(!ping.found?(f, ''))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
62
71
|
def test_relative_path
|
63
72
|
assert_raises do
|
64
73
|
GptTranslate::Ping.new({}, '404.html')
|
data/test/test_plain.rb
CHANGED
@@ -47,6 +47,20 @@ class GptTranslate::PlainTest < Minitest::Test
|
|
47
47
|
)
|
48
48
|
end
|
49
49
|
|
50
|
+
def test_ordered_list
|
51
|
+
assert_equal(
|
52
|
+
"first\n\nsecond\n\nthird",
|
53
|
+
GptTranslate::Plain.new("1. first\n\n2. second\n\n3. third").to_s
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_compact_list
|
58
|
+
assert_equal(
|
59
|
+
"first\n\nsecond\n\nthird",
|
60
|
+
GptTranslate::Plain.new("* first\n* second\n* third").to_s
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
50
64
|
def test_links
|
51
65
|
assert_equal(
|
52
66
|
'Hello, dude!',
|
@@ -89,5 +103,20 @@ class GptTranslate::PlainTest < Minitest::Test
|
|
89
103
|
'Hello, !',
|
90
104
|
GptTranslate::Plain.new('Hello, {% Java %}!').to_s
|
91
105
|
)
|
106
|
+
assert_equal(
|
107
|
+
'Hello, !',
|
108
|
+
GptTranslate::Plain.new('Hello, {% plantuml "width=50%" %}!').to_s
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_html_comments
|
113
|
+
assert_equal(
|
114
|
+
'Hello, !',
|
115
|
+
GptTranslate::Plain.new('Hello, <!-- Java -->!').to_s
|
116
|
+
)
|
117
|
+
assert_equal(
|
118
|
+
'Hello, !',
|
119
|
+
GptTranslate::Plain.new("Hello, <!-- \nJava\n -->!").to_s
|
120
|
+
)
|
92
121
|
end
|
93
122
|
end
|