jekyll-chatgpt-translate 0.0.16 → 0.0.18
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/README.md +18 -5
- data/features/cli.feature +5 -4
- data/features/gem_package.feature +1 -1
- data/features/step_definitions/steps.rb +16 -20
- data/jekyll-chatgpt-translate.gemspec +1 -1
- data/lib/jekyll-chatgpt-translate/generator.rb +37 -35
- data/lib/jekyll-chatgpt-translate/plain.rb +2 -0
- data/lib/jekyll-chatgpt-translate/version.rb +1 -1
- data/test/test_generator.rb +47 -25
- data/test/test_plain.rb +4 -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: 1f593e5927f89dc9fe25dcbe28cc61ed938b3fdc42aee742d9ec31ebc86417fe
|
4
|
+
data.tar.gz: c5486a9adaf6e838bd016ea01175169786dbdd1807601cf922b0acc065c0a337
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 794664182ebba232506ab97b026d02a7060ee369fb16756308d43362df24aed862703836fb0af142c07240fd1eeeeeb9305e43804ed5b34300ce2dff1c30a03c
|
7
|
+
data.tar.gz: f1db90b466e6ea441519ee35351a2bf62b9182ed772ab020b5cd4692fc8932a22115fb994089b1cb70724c92a4344865810312b7a58fe1fe666ba0e5c308aaeb
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ If you have a [Jekyll](https://jekyllrb.com/) static site, this plugin may help
|
|
7
7
|
translate its pages to another language, through [ChatGPT](https://chat.openai.com/). See how it
|
8
8
|
works for [my blog](https://github.com/yegor256/ru.yegor256.com),
|
9
9
|
for example [this page](https://ru.yegor256.com/2023-08-13-dictators.html) is translated to
|
10
|
-
[English]().
|
10
|
+
[English](https://ru.yegor256.com/english/2023-08-13-dictators.html).
|
11
11
|
|
12
12
|
Install it first:
|
13
13
|
|
@@ -18,13 +18,16 @@ gem install jekyll-chatgpt-translate
|
|
18
18
|
Then, add this to `_config.yml`:
|
19
19
|
|
20
20
|
```yaml
|
21
|
+
plugins:
|
22
|
+
- ... your other plugins here ...
|
23
|
+
- jekyll-chatgpt-translate
|
21
24
|
chatgpt-translate:
|
22
25
|
model: gpt-3.5-turbo
|
23
26
|
source: en
|
24
27
|
layout: translated
|
25
28
|
targets:
|
26
29
|
-
|
27
|
-
language:
|
30
|
+
language: zh
|
28
31
|
permalink: :year-:month-:day-:slug-chinese.html
|
29
32
|
layout: chinese-translated
|
30
33
|
-
|
@@ -32,7 +35,7 @@ chatgpt-translate:
|
|
32
35
|
permalink: :year-:month-:day-:title-french.html
|
33
36
|
```
|
34
37
|
|
35
|
-
Here, the source language is English (`en`), the target one is Chinese (`
|
38
|
+
Here, the source language is English (`en`), the target one is Chinese (`zh`),
|
36
39
|
the layout is `_layout/translated.html` (you must have this file).
|
37
40
|
|
38
41
|
OpenAI API KEY must be set in `OPENAI_API_KEY` environment variable, otherwise
|
@@ -40,10 +43,15 @@ the plugin will not do any translation and won't generate translated pages.
|
|
40
43
|
You can get your key [here](https://help.openai.com/en/articles/4936850-where-do-i-find-my-secret-api-key).
|
41
44
|
|
42
45
|
Inside the original page you can use `{{ page.translated-XX-url }}` in order to render the URL
|
43
|
-
of the translated page, where `XX` is the ISO-
|
46
|
+
of the translated page, where `XX` is the [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
47
|
+
code of the target language.
|
44
48
|
Inside the translated page you can use `{{ page.translated-original-url }}` in order
|
45
|
-
to get the URL of the page that was translated.
|
49
|
+
to get the URL of the page that was translated.
|
50
|
+
|
51
|
+
You can also use `{{ page.chatgpt-model }}`
|
46
52
|
inside both the original page and the translated one, to refer to the model of ChatGPT.
|
53
|
+
The presence of this attribute in the `{{ page }}` means that the
|
54
|
+
page was translated or the translated HTML was downloaded and placed into the `_site` directory.
|
47
55
|
|
48
56
|
## Options
|
49
57
|
|
@@ -56,6 +64,11 @@ Full list of options available to specify in `_config.yml`:
|
|
56
64
|
|
57
65
|
* `source` (optional) — is the ISO-839-1 code of the source language.
|
58
66
|
|
67
|
+
* `no_download` (optional) — if this attribute is present, the plugin won't try
|
68
|
+
to find HTML versions of translated pages in the Internet and won't try to
|
69
|
+
download them and place into the `_site` directory. Thus, your entire site
|
70
|
+
will have to be re-translated on every build (might be very ineffective if the site is big!)
|
71
|
+
|
59
72
|
* `layout` (optional) — is name of the file in `_layouts` directory, without the extension.
|
60
73
|
This layout will be specified for the pages generated by this plugin.
|
61
74
|
|
data/features/cli.feature
CHANGED
@@ -13,7 +13,7 @@ Feature: Simple site building
|
|
13
13
|
layout: translated
|
14
14
|
targets:
|
15
15
|
-
|
16
|
-
language:
|
16
|
+
language: zh
|
17
17
|
permalink: :year-:month-:day-:slug-chinese.html
|
18
18
|
layout: chinese-translated
|
19
19
|
-
|
@@ -22,7 +22,7 @@ Feature: Simple site building
|
|
22
22
|
"""
|
23
23
|
And I have a "_layouts/default.html" file with content:
|
24
24
|
"""
|
25
|
-
The Chinese: {{ page.translated-
|
25
|
+
The Chinese: {{ page.translated-zh-url }}
|
26
26
|
The French: {{ page.translated-fr-url }}
|
27
27
|
{{ content }}
|
28
28
|
"""
|
@@ -40,8 +40,9 @@ Feature: Simple site building
|
|
40
40
|
Hello, world!
|
41
41
|
"""
|
42
42
|
Then I build Jekyll site
|
43
|
-
And File "_chatgpt-translated/
|
44
|
-
And File "_chatgpt-translated/
|
43
|
+
And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" exists
|
44
|
+
And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" contains "/2023-01-01-hello-chinese.html"
|
45
|
+
And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" contains "translated-language: \"zh\""
|
45
46
|
And File "_site/2023/01/01/hello.html" exists
|
46
47
|
And File "_site/2023/01/01/hello.html" contains "The Chinese: /2023-01-01-hello-chinese.html"
|
47
48
|
And File "_site/2023-01-01-hello-chinese.html" exists
|
@@ -40,39 +40,35 @@ Given(/^I have a "([^"]*)" file with content:$/) do |file, text|
|
|
40
40
|
File.write(file, text.gsub('\\xFF', 0xFF.chr))
|
41
41
|
end
|
42
42
|
|
43
|
-
When(
|
43
|
+
When('I build Jekyll site') do
|
44
44
|
@stdout = `jekyll build`
|
45
45
|
@exitstatus = $CHILD_STATUS.exitstatus
|
46
46
|
end
|
47
47
|
|
48
|
-
Then(
|
49
|
-
raise "STDOUT doesn't contain '#{
|
48
|
+
Then('Stdout contains {string}') do |string|
|
49
|
+
raise "STDOUT doesn't contain '#{string}':\n#{@stdout}" unless @stdout.include?(string)
|
50
50
|
end
|
51
51
|
|
52
|
-
Then(
|
53
|
-
raise "The file \"#{
|
52
|
+
Then('File {string} exists') do |string|
|
53
|
+
raise "The file \"#{string}\" is absent:\n#{`tree -s`}" unless File.exist?(string)
|
54
54
|
end
|
55
55
|
|
56
|
-
Then(
|
57
|
-
raise "The file \"#{
|
58
|
-
content = File.read(
|
59
|
-
raise "The file \"#{
|
56
|
+
Then('File {string} contains {string}') do |string, string2|
|
57
|
+
raise "The file \"#{string}\" is absent" unless File.exist?(string)
|
58
|
+
content = File.read(string)
|
59
|
+
raise "The file \"#{string}\" doesn't contain \"#{string2}\":\n#{content}" unless content.include?(string2)
|
60
60
|
end
|
61
61
|
|
62
|
-
Then(
|
63
|
-
raise "STDOUT is not empty:\n#{@stdout}" unless @stdout == ''
|
64
|
-
end
|
65
|
-
|
66
|
-
Then(/^Exit code is zero$/) do
|
62
|
+
Then('Exit code is zero') do
|
67
63
|
raise "Non-zero exit #{@exitstatus}:\n#{@stdout}" unless @exitstatus.zero?
|
68
64
|
end
|
69
65
|
|
70
|
-
Then(
|
66
|
+
Then('Exit code is not zero') do
|
71
67
|
raise 'Zero exit code' if @exitstatus.zero?
|
72
68
|
end
|
73
69
|
|
74
|
-
When(
|
75
|
-
@stdout = `#{
|
70
|
+
When('I run bash with {string}') do |string|
|
71
|
+
@stdout = `#{string}`
|
76
72
|
@exitstatus = $CHILD_STATUS.exitstatus
|
77
73
|
end
|
78
74
|
|
@@ -81,14 +77,14 @@ When(/^I run bash with:$/) do |text|
|
|
81
77
|
@exitstatus = $CHILD_STATUS.exitstatus
|
82
78
|
end
|
83
79
|
|
84
|
-
When(
|
80
|
+
When('I copy this gem into temp dir') do
|
85
81
|
FileUtils.copy_entry(@cwd, File.join(@dir, 'jekyll-chatgpt-translate'))
|
86
82
|
end
|
87
83
|
|
88
|
-
Given(
|
84
|
+
Given('It is Unix') do
|
89
85
|
pending if Gem.win_platform?
|
90
86
|
end
|
91
87
|
|
92
|
-
Given(
|
88
|
+
Given('It is Windows') do
|
93
89
|
pending unless Gem.win_platform?
|
94
90
|
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.18'
|
32
32
|
s.license = 'MIT'
|
33
33
|
s.summary = 'Translate Jekyll Pages Through ChatGPT'
|
34
34
|
s.description = [
|
@@ -54,7 +54,8 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
54
54
|
version = config['version'] || GptTranslate::VERSION
|
55
55
|
threshold = config['threshold'] || 1024
|
56
56
|
start = Time.now
|
57
|
-
|
57
|
+
translated = 0
|
58
|
+
copied = 0
|
58
59
|
model = config['model'] || 'gpt-3.5-turbo'
|
59
60
|
marker = "Translated by ChatGPT #{model}/#{version}"
|
60
61
|
site.posts.docs.shuffle.each do |doc|
|
@@ -63,48 +64,49 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
63
64
|
link = GptTranslate::Permalink.new(doc, target['permalink']).to_path
|
64
65
|
lang = target['language']
|
65
66
|
raise 'Language must be defined for each target' if target.nil?
|
66
|
-
if total >= threshold
|
67
|
-
Jekyll.logger.info("Already generated #{total} pages, that's enough for today")
|
68
|
-
break
|
69
|
-
end
|
70
67
|
path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
|
71
68
|
FileUtils.mkdir_p(File.dirname(path))
|
72
69
|
File.write(path, '') # in order to surpress warnings in Page ctor
|
73
70
|
dest = Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path)).destination(site.dest)
|
71
|
+
if config['no_download'].nil? && GptTranslate::Ping.new(site, link).found?(dest, version)
|
72
|
+
copied += 1
|
73
|
+
elsif translated >= threshold
|
74
|
+
next
|
75
|
+
else
|
76
|
+
gpt = GptTranslate::ChatGPT.new(
|
77
|
+
key,
|
78
|
+
model,
|
79
|
+
config['source'] || 'en',
|
80
|
+
lang
|
81
|
+
)
|
82
|
+
foreign = gpt.translate(plain)
|
83
|
+
File.write(
|
84
|
+
path,
|
85
|
+
[
|
86
|
+
'---',
|
87
|
+
"layout: #{target['layout'] || layout}",
|
88
|
+
"title: #{doc['title'].to_json}",
|
89
|
+
"description: #{doc['description'].to_json}",
|
90
|
+
"permalink: #{link.to_json}",
|
91
|
+
"translated-original-url: #{doc.url.to_json}",
|
92
|
+
"translated-language: #{lang.to_json}",
|
93
|
+
"chatgpt-model: #{model.to_json}",
|
94
|
+
'---',
|
95
|
+
'',
|
96
|
+
foreign,
|
97
|
+
'',
|
98
|
+
"#{marker} on #{Time.now.strftime('%d/%m/%Y %H:%M')}\n{: .jekyll-chatgpt-translate}"
|
99
|
+
].join("\n")
|
100
|
+
)
|
101
|
+
site.pages << Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path))
|
102
|
+
translated += 1
|
103
|
+
Jekyll.logger.info("Translated via ChatGPT: #{path} (#{File.size(path)} bytes)")
|
104
|
+
end
|
74
105
|
doc.data["translated-#{lang}-url"] = link
|
75
106
|
doc.data['chatgpt-model'] = model
|
76
|
-
next if GptTranslate::Ping.new(site, link).found?(dest, version)
|
77
|
-
gpt = GptTranslate::ChatGPT.new(
|
78
|
-
key,
|
79
|
-
model,
|
80
|
-
config['source'] || 'en',
|
81
|
-
lang
|
82
|
-
)
|
83
|
-
translated = gpt.translate(plain)
|
84
|
-
File.write(
|
85
|
-
path,
|
86
|
-
[
|
87
|
-
'---',
|
88
|
-
"layout: #{target['layout'] || layout}",
|
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}",
|
94
|
-
'---',
|
95
|
-
'',
|
96
|
-
translated,
|
97
|
-
'',
|
98
|
-
"#{marker} on #{Time.now.strftime('%d/%m/%Y %H:%M')}\n{: .jekyll-chatgpt-translate}"
|
99
|
-
].join("\n")
|
100
|
-
)
|
101
|
-
site.pages << Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path))
|
102
|
-
total += 1
|
103
|
-
Jekyll.logger.info("Translated via ChatGPT: #{path}")
|
104
107
|
end
|
105
|
-
break if total >= threshold
|
106
108
|
end
|
107
|
-
Jekyll.logger.info("#{
|
109
|
+
Jekyll.logger.info("#{translated} pages translated and #{copied} pages copied in #{(Time.now - start).round(2)}s")
|
108
110
|
end
|
109
111
|
|
110
112
|
private
|
data/test/test_generator.rb
CHANGED
@@ -33,19 +33,16 @@ require_relative '../lib/jekyll-chatgpt-translate/generator'
|
|
33
33
|
# License:: MIT
|
34
34
|
class GptTranslate::GeneratorTest < Minitest::Test
|
35
35
|
class FakeSite
|
36
|
-
attr_reader :config
|
36
|
+
attr_reader :config, :pages
|
37
37
|
|
38
|
-
def initialize(config,
|
38
|
+
def initialize(config, docs)
|
39
39
|
@config = config
|
40
|
-
@
|
40
|
+
@docs = docs
|
41
|
+
@pages = []
|
41
42
|
end
|
42
43
|
|
43
44
|
def posts
|
44
|
-
FakePosts.new(@
|
45
|
-
end
|
46
|
-
|
47
|
-
def pages
|
48
|
-
[]
|
45
|
+
FakePosts.new(@docs)
|
49
46
|
end
|
50
47
|
|
51
48
|
def permalink_style
|
@@ -65,7 +62,7 @@ class GptTranslate::GeneratorTest < Minitest::Test
|
|
65
62
|
end
|
66
63
|
|
67
64
|
def dest
|
68
|
-
|
65
|
+
File.dirname(@docs[0])
|
69
66
|
end
|
70
67
|
|
71
68
|
def in_theme_dir(base, _foo = nil, _bar = nil)
|
@@ -78,28 +75,23 @@ class GptTranslate::GeneratorTest < Minitest::Test
|
|
78
75
|
end
|
79
76
|
|
80
77
|
class FakeDocument
|
78
|
+
attr_reader :data
|
79
|
+
|
81
80
|
def initialize(path)
|
82
81
|
@path = path
|
82
|
+
@data = { 'date' => Time.now, 'title' => 'Hello!' }
|
83
83
|
end
|
84
84
|
|
85
85
|
def content
|
86
86
|
'Hello, world!'
|
87
87
|
end
|
88
88
|
|
89
|
-
def
|
90
|
-
|
89
|
+
def []=(key, value)
|
90
|
+
@data[key] = value
|
91
91
|
end
|
92
92
|
|
93
|
-
def []=(key, value); end
|
94
|
-
|
95
93
|
def [](key)
|
96
|
-
|
97
|
-
Time.now
|
98
|
-
elsif key == 'title'
|
99
|
-
'Hello!'
|
100
|
-
else
|
101
|
-
''
|
102
|
-
end
|
94
|
+
@data[key] || ''
|
103
95
|
end
|
104
96
|
|
105
97
|
def relative_path
|
@@ -118,12 +110,12 @@ class GptTranslate::GeneratorTest < Minitest::Test
|
|
118
110
|
class FakePosts
|
119
111
|
attr_reader :config
|
120
112
|
|
121
|
-
def initialize(
|
122
|
-
@
|
113
|
+
def initialize(docs)
|
114
|
+
@docs = docs
|
123
115
|
end
|
124
116
|
|
125
117
|
def docs
|
126
|
-
|
118
|
+
@docs.map { |d| FakeDocument.new(d) }
|
127
119
|
end
|
128
120
|
end
|
129
121
|
|
@@ -137,18 +129,48 @@ class GptTranslate::GeneratorTest < Minitest::Test
|
|
137
129
|
'chatgpt-translate' => {
|
138
130
|
'targets' => [
|
139
131
|
{
|
140
|
-
'language' => '
|
132
|
+
'language' => 'zh',
|
141
133
|
'layout' => 'chinese',
|
142
134
|
'permalink' => ':slug.html'
|
143
135
|
}
|
144
136
|
]
|
145
137
|
}
|
146
138
|
},
|
147
|
-
|
139
|
+
[post]
|
140
|
+
)
|
141
|
+
gen = GptTranslate::Generator.new
|
142
|
+
stub_request(:get, 'https://www.yegor256.com/.html').to_return(body: '')
|
143
|
+
gen.generate(site)
|
144
|
+
assert_equal(1, site.pages.count)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_threshold_stops
|
149
|
+
Dir.mktmpdir do |home|
|
150
|
+
post = File.join(home, '2023-01-01-hello.md')
|
151
|
+
File.write(post, "---\ntitle: Hello\n---\n\nHello, world!")
|
152
|
+
site = FakeSite.new(
|
153
|
+
{
|
154
|
+
'chatgpt-translate' => {
|
155
|
+
'threshold' => 1,
|
156
|
+
'targets' => [
|
157
|
+
{
|
158
|
+
'language' => 'zh',
|
159
|
+
'permalink' => ':slug.html'
|
160
|
+
},
|
161
|
+
{
|
162
|
+
'language' => 'fr',
|
163
|
+
'permalink' => ':year/:slug.html'
|
164
|
+
}
|
165
|
+
]
|
166
|
+
}
|
167
|
+
},
|
168
|
+
[post, post]
|
148
169
|
)
|
149
170
|
gen = GptTranslate::Generator.new
|
150
171
|
stub_request(:get, 'https://www.yegor256.com/.html').to_return(body: '')
|
151
172
|
gen.generate(site)
|
173
|
+
assert_equal(1, site.pages.count)
|
152
174
|
end
|
153
175
|
end
|
154
176
|
end
|
data/test/test_plain.rb
CHANGED
@@ -96,6 +96,10 @@ class GptTranslate::PlainTest < Minitest::Test
|
|
96
96
|
"```\nHello\n```",
|
97
97
|
GptTranslate::Plain.new("```\nHello\n```").to_s
|
98
98
|
)
|
99
|
+
assert_equal(
|
100
|
+
"```\nprint('hi!')\n```",
|
101
|
+
GptTranslate::Plain.new("```java\nprint('hi!')\n```").to_s
|
102
|
+
)
|
99
103
|
end
|
100
104
|
|
101
105
|
def test_liquid_tags
|