jekyll-chatgpt-translate 0.0.6 → 0.0.8
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/.rubocop.yml +6 -0
- data/Gemfile +2 -1
- data/README.md +11 -5
- data/features/cli.feature +11 -4
- data/features/step_definitions/steps.rb +6 -1
- data/jekyll-chatgpt-translate.gemspec +1 -1
- data/lib/jekyll-chatgpt-translate/chatgpt.rb +9 -14
- data/lib/jekyll-chatgpt-translate/generator.rb +26 -14
- data/lib/jekyll-chatgpt-translate/permalink.rb +5 -2
- data/lib/jekyll-chatgpt-translate/ping.rb +4 -3
- data/logo.png +0 -0
- data/test/test_chatgpt.rb +8 -0
- data/test/test_permalink.rb +6 -6
- data/test/test_ping.rb +15 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae5a29abd8e4cb757fa40d7b50c999a4453901b1be9a597f23e560fa3900416d
|
4
|
+
data.tar.gz: e1769e8544c657ad086c462a1d32652edd434ac26509b064d1441f8beb35ea28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97ac19fd067d197faf5fb5c2b541a0641b56c0ca0fbbef3f9fbfe02c4efacb89657fc9d9011bd10081ce6221807eba792be5ed8f12e9617a197c36574f668df4
|
7
|
+
data.tar.gz: 2f5251871b78f3052f33b7c1778318d89945b9874295fc36da9ae8f8d7fb34a720bb475210f0e1546ccf18c7a136ee2ed5e24d70aaa4630d5fe7e1f707d95bac
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
@@ -29,6 +29,7 @@ gem 'cucumber', '8.0.0', require: false
|
|
29
29
|
gem 'kramdown-parser-gfm', '1.1.0', require: false
|
30
30
|
gem 'minitest', '5.19.0', require: false
|
31
31
|
gem 'rake', '13.0.6', require: false
|
32
|
-
gem 'rubocop', '1.
|
32
|
+
gem 'rubocop', '1.56.0', require: false
|
33
33
|
gem 'rubocop-rspec', '2.23.2', require: false
|
34
34
|
gem 'simplecov', '0.22.0', require: false
|
35
|
+
gem 'webmock', '3.18.1', require: false
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
<img src="logo.png" style="width:256px;"/>
|
2
|
+
|
1
3
|
[](https://github.com/yegor256/jekyll-chatgpt-translate/actions/workflows/rake.yml)
|
2
4
|
[](http://badge.fury.io/rb/jekyll-chatgpt-translate)
|
3
5
|
|
@@ -20,7 +22,7 @@ chatgpt-translate:
|
|
20
22
|
targets:
|
21
23
|
-
|
22
24
|
language: cn
|
23
|
-
permalink: :year-:month-:day-:
|
25
|
+
permalink: :year-:month-:day-:slug-chinese.html
|
24
26
|
layout: chinese-translated
|
25
27
|
-
|
26
28
|
language: fr
|
@@ -34,14 +36,18 @@ OpenAI API KEY must be set in `OPENAI_API_KEY` environment variable, otherwise
|
|
34
36
|
the plugin will not do any translation and won't generate translated pages.
|
35
37
|
You can get your key [here](https://help.openai.com/en/articles/4936850-where-do-i-find-my-secret-api-key).
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
Inside the original page you can use `{{ page.translated-XX-url }}` in order to render the URL
|
40
|
+
of the translated page, where `XX` is the ISO-839-1 code of the target language..
|
41
|
+
Inside the translated page you can use `{{ page.translated-original-url }}` in order
|
42
|
+
to get the URL of the page that was translated.
|
40
43
|
|
41
44
|
## Options
|
42
45
|
|
43
46
|
Full list of options available to specify in `_config.yml`:
|
44
47
|
|
48
|
+
* `api_key_file` (optional) — the file with OpenAI API key. If this option is not specified,
|
49
|
+
it is expected to have the key in the `OPENAI_API_KEY` environment variable.
|
50
|
+
|
45
51
|
* `model` (optional) — specifies the model to use by ChatGPT.
|
46
52
|
|
47
53
|
* `source` (optional) — is the ISO-839-1 code of the source language.
|
@@ -61,7 +67,7 @@ This layout will be specified for the pages generated by this plugin.
|
|
61
67
|
|
62
68
|
## How to Contribute
|
63
69
|
|
64
|
-
|
70
|
+
Make a fork and then test it locally like this:
|
65
71
|
|
66
72
|
```bash
|
67
73
|
$ bundle update
|
data/features/cli.feature
CHANGED
@@ -13,30 +13,37 @@ Feature: Simple site building
|
|
13
13
|
targets:
|
14
14
|
-
|
15
15
|
language: cn
|
16
|
-
permalink: :year-:month-:day-:
|
16
|
+
permalink: :year-:month-:day-:slug-chinese.html
|
17
17
|
layout: chinese-translated
|
18
18
|
-
|
19
19
|
language: fr
|
20
|
-
permalink: :year/:
|
20
|
+
permalink: :year/:slug-french.html
|
21
21
|
"""
|
22
22
|
And I have a "_layouts/default.html" file with content:
|
23
23
|
"""
|
24
|
+
The Chinese: {{ page.translated-cn-url }}
|
25
|
+
The French: {{ page.translated-fr-url }}
|
24
26
|
{{ content }}
|
25
27
|
"""
|
26
28
|
And I have a "_layouts/chinese-translated.html" file with content:
|
27
29
|
"""
|
28
30
|
Chinese: {{ content }}
|
31
|
+
The original: {{ page.translated-original-url }}
|
29
32
|
"""
|
30
33
|
And I have a "_posts/2023-01-01-hello.md" file with content:
|
31
34
|
"""
|
32
35
|
---
|
36
|
+
title: Hello, world!
|
33
37
|
layout: default
|
34
38
|
---
|
35
39
|
Hello, world!
|
36
40
|
"""
|
37
41
|
Then I build Jekyll site
|
42
|
+
Then File "_chatgpt-translated/cn/2023-01-01-hello-cn.md" exists
|
38
43
|
Then File "_site/2023/01/01/hello.html" exists
|
39
|
-
Then File "_site/2023-01-01-
|
40
|
-
Then File "_site/2023
|
44
|
+
Then File "_site/2023/01/01/hello.html" contains "The Chinese: /2023-01-01-hello-chinese.html"
|
45
|
+
Then File "_site/2023-01-01-hello-chinese.html" exists
|
46
|
+
Then File "_site/2023-01-01-hello-chinese.html" contains "The original: /2023/01/01/hello.html"
|
47
|
+
Then File "_site/2023/hello-french.html" exists
|
41
48
|
And Exit code is zero
|
42
49
|
|
@@ -50,7 +50,12 @@ Then(/^Stdout contains "([^"]*)"$/) do |txt|
|
|
50
50
|
end
|
51
51
|
|
52
52
|
Then(/^File "([^"]*)" exists$/) do |name|
|
53
|
-
raise unless File.exist?(name)
|
53
|
+
raise "The file \"#{name}\" is absent:\n#{`tree`}" unless File.exist?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
Then(/^File "([^"]*)" contains "([^"]*)"$/) do |name, text|
|
57
|
+
content = File.read(name)
|
58
|
+
raise "The file \"#{name}\" doesn't contain \"#{text}\":\n#{content}" unless content.include?(text)
|
54
59
|
end
|
55
60
|
|
56
61
|
Then(/^Stdout is empty$/) do
|
@@ -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.8'
|
32
32
|
s.license = 'MIT'
|
33
33
|
s.summary = 'Translate Jekyll Pages Through ChatGPT'
|
34
34
|
s.description = [
|
@@ -65,7 +65,7 @@ class GptTranslate::ChatGPT
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def translate_par(par)
|
68
|
-
Time.now
|
68
|
+
start = Time.now
|
69
69
|
t = nil
|
70
70
|
attempt = 0
|
71
71
|
begin
|
@@ -80,26 +80,21 @@ class GptTranslate::ChatGPT
|
|
80
80
|
}
|
81
81
|
)
|
82
82
|
t = response.dig('choices', 0, 'message', 'content')
|
83
|
-
rescue StandardError
|
83
|
+
rescue StandardError => e
|
84
84
|
attempt += 1
|
85
85
|
retry if attempt < 4
|
86
|
+
raise e
|
86
87
|
end
|
87
|
-
|
88
|
-
|
89
|
-
#{@
|
90
|
-
|
91
|
-
else
|
92
|
-
Jekyll.logger.debug("Translated #{par.split.count} #{@source.upcase} words
|
93
|
-
to #{t.split.count} #{@target.upcase} words
|
94
|
-
through #{@model} in #{(Time.now - pstart).round(2)}s")
|
95
|
-
t
|
96
|
-
end
|
88
|
+
Jekyll.logger.info("Translated #{par.split.count} #{@source.upcase} words \
|
89
|
+
to #{t.split.count} #{@target.upcase} words \
|
90
|
+
through #{@model} in #{(Time.now - start).round(2)}s")
|
91
|
+
t
|
97
92
|
end
|
98
93
|
|
99
94
|
def prompt
|
100
|
-
if (source == 'ru') && (target == 'en')
|
95
|
+
if (@source == 'ru') && (@target == 'en')
|
101
96
|
'Пожалуйста, переведи этот параграф на английский язык'
|
102
|
-
elsif (source == 'en') && (target == 'ru')
|
97
|
+
elsif (@source == 'en') && (@target == 'ru')
|
103
98
|
'Please, translate this paragraph to Russian'
|
104
99
|
else
|
105
100
|
[
|
@@ -42,14 +42,9 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
42
42
|
|
43
43
|
# Main plugin action, called by Jekyll-core
|
44
44
|
def generate(site)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Jekyll.logger.info('OPENAI_API_KEY environment variable is not set and
|
49
|
-
we are in development mode, no actual translation will happen,
|
50
|
-
but pages will be generated')
|
51
|
-
key = ''
|
52
|
-
end
|
45
|
+
config ||= site.config['chatgpt-translate'] || {}
|
46
|
+
home = '_chatgpt-translated'
|
47
|
+
key = api_key(config)
|
53
48
|
if key.nil?
|
54
49
|
Jekyll.logger.info('jekyll-chatgpt-translate requires OPENAI_API_KEY environment variable')
|
55
50
|
return
|
@@ -61,7 +56,7 @@ but pages will be generated')
|
|
61
56
|
site.posts.docs.each do |doc|
|
62
57
|
plain = GptTranslate::Plain.new(doc.content).to_s
|
63
58
|
config['targets'].each do |target|
|
64
|
-
link = GptTranslate::Permalink.new(doc, target['permalink']).
|
59
|
+
link = GptTranslate::Permalink.new(doc, target['permalink']).to_path
|
65
60
|
next if GptTranslate::Ping.new(site, link).exists?
|
66
61
|
lang = target['language']
|
67
62
|
raise 'Language must be defined for each target' if target.nil?
|
@@ -77,7 +72,7 @@ but pages will be generated')
|
|
77
72
|
lang
|
78
73
|
)
|
79
74
|
translated = gpt.translate(plain)
|
80
|
-
path =
|
75
|
+
path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
|
81
76
|
FileUtils.mkdir_p(File.dirname(path))
|
82
77
|
File.write(
|
83
78
|
path,
|
@@ -86,6 +81,7 @@ but pages will be generated')
|
|
86
81
|
"layout: #{target['layout'] || layout}",
|
87
82
|
"title: #{doc.data['title']}",
|
88
83
|
"permalink: #{link}",
|
84
|
+
"translated-original-url: #{doc.url}",
|
89
85
|
'---',
|
90
86
|
'',
|
91
87
|
translated,
|
@@ -93,18 +89,34 @@ but pages will be generated')
|
|
93
89
|
"Translated by ChatGPT #{model}/#{GptTranslate::VERSION}\n{: .jekyll-chatgpt-translate}"
|
94
90
|
].join("\n")
|
95
91
|
)
|
92
|
+
doc.data["translated-#{lang}-url"] = link
|
96
93
|
site.pages << Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path))
|
97
94
|
total += 1
|
95
|
+
Jekyll.logger.info("Translated via ChatGPT: #{path}")
|
98
96
|
end
|
99
97
|
break if total >= threshold
|
100
98
|
end
|
101
|
-
Jekyll.logger.info("#{total} pages
|
99
|
+
Jekyll.logger.info("#{total} pages translated in #{(Time.now - start).round(2)}s")
|
102
100
|
end
|
103
101
|
|
104
102
|
private
|
105
103
|
|
106
|
-
#
|
107
|
-
|
108
|
-
|
104
|
+
# Try to find the KEY, either in the environment, a file, etc.
|
105
|
+
# If not found, return NIL.
|
106
|
+
def api_key(config)
|
107
|
+
file = config['api_key_file']
|
108
|
+
key = if file.nil?
|
109
|
+
ENV.fetch('OPENAI_API_KEY', nil)
|
110
|
+
else
|
111
|
+
File.read(file).strip
|
112
|
+
end
|
113
|
+
if key.nil? && Jekyll.env == 'development'
|
114
|
+
Jekyll.logger.info("OPENAI_API_KEY environment variable is not set, \
|
115
|
+
the `api_key_file` option is not specified in the _config.yml, and \
|
116
|
+
we are in development mode, that's why no actual translation will happen, \
|
117
|
+
but .md pages will be generated")
|
118
|
+
key = ''
|
119
|
+
end
|
120
|
+
key
|
109
121
|
end
|
110
122
|
end
|
@@ -38,11 +38,14 @@ class GptTranslate::Permalink
|
|
38
38
|
@template = template
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
@template
|
41
|
+
def to_path
|
42
|
+
path = @template
|
43
43
|
.gsub(':year', format('%04d', @doc['date'].year))
|
44
44
|
.gsub(':month', format('%02d', @doc['date'].month))
|
45
45
|
.gsub(':day', format('%02d', @doc['date'].day))
|
46
46
|
.gsub(':title', CGI.escape(@doc['title']))
|
47
|
+
.gsub(':slug', CGI.escape(@doc['slug']))
|
48
|
+
path = "/#{path}" unless path.start_with?('/')
|
49
|
+
path
|
47
50
|
end
|
48
51
|
end
|
@@ -40,15 +40,16 @@ module GptTranslate; end
|
|
40
40
|
# License:: MIT
|
41
41
|
class GptTranslate::Ping
|
42
42
|
# Ctor.
|
43
|
-
def initialize(site,
|
43
|
+
def initialize(site, path)
|
44
44
|
@site = site
|
45
|
-
|
45
|
+
raise 'Permalink must start with a slash' unless path.start_with?('/')
|
46
|
+
@path = path
|
46
47
|
end
|
47
48
|
|
48
49
|
def exists?
|
49
50
|
home = @site.config['url']
|
50
51
|
return false if home.nil?
|
51
|
-
uri = Iri.new(home).path(@
|
52
|
+
uri = Iri.new(home).path(@path)
|
52
53
|
before = Net::HTTP.get_response(URI(uri.to_s))
|
53
54
|
if before.is_a?(Net::HTTPSuccess) && before.body.include?("/#{GptTranslate::VERSION}")
|
54
55
|
Jekyll.logger.info("No need to translate, page exists at #{uri} (#{before.body.split.count} words)")
|
data/logo.png
ADDED
Binary file
|
data/test/test_chatgpt.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
# SOFTWARE.
|
24
24
|
|
25
25
|
require 'minitest/autorun'
|
26
|
+
require 'webmock/minitest'
|
26
27
|
require_relative '../lib/jekyll-chatgpt-translate/chatgpt'
|
27
28
|
|
28
29
|
# ChatGPT test.
|
@@ -44,4 +45,11 @@ class GptTranslate::ChatGPTTest < Minitest::Test
|
|
44
45
|
chat = GptTranslate::ChatGPT.new('fake-key', 'gpt-3.5-turbo', 'en', 'ru')
|
45
46
|
assert_equal('<img src="a"/>', chat.translate('<img src="a"/>'))
|
46
47
|
end
|
48
|
+
|
49
|
+
def test_through_webmock
|
50
|
+
stub_request(:any, 'https://api.openai.com/v1/chat/completions')
|
51
|
+
.to_return(body: '{"choices":[{"message":{"content": "boom!"}}]}')
|
52
|
+
chat = GptTranslate::ChatGPT.new('fake-key', 'gpt-3.5-turbo', 'en', 'ru')
|
53
|
+
assert_equal('boom!', chat.translate('This is the text to send to OpenAI'))
|
54
|
+
end
|
47
55
|
end
|
data/test/test_permalink.rb
CHANGED
@@ -34,9 +34,9 @@ class GptTranslate::PermalinkTest < Minitest::Test
|
|
34
34
|
assert_equal(
|
35
35
|
'/2023.html',
|
36
36
|
GptTranslate::Permalink.new(
|
37
|
-
{ 'date' => Time.parse('2023-01-01'), 'title' => 'Hello' },
|
38
|
-
'
|
39
|
-
).
|
37
|
+
{ 'date' => Time.parse('2023-01-01'), 'title' => 'Hello', 'slug' => 'hello' },
|
38
|
+
':year.html'
|
39
|
+
).to_path
|
40
40
|
)
|
41
41
|
end
|
42
42
|
|
@@ -44,9 +44,9 @@ class GptTranslate::PermalinkTest < Minitest::Test
|
|
44
44
|
assert_equal(
|
45
45
|
'/2023-%23%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82.html',
|
46
46
|
GptTranslate::Permalink.new(
|
47
|
-
{ 'date' => Time.parse('2023-01-01'), 'title' => '#привет' },
|
48
|
-
'
|
49
|
-
).
|
47
|
+
{ 'date' => Time.parse('2023-01-01'), 'title' => '#привет', 'slug' => 'hello' },
|
48
|
+
':year-:title.html'
|
49
|
+
).to_path
|
50
50
|
)
|
51
51
|
end
|
52
52
|
end
|
data/test/test_ping.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
# SOFTWARE.
|
24
24
|
|
25
25
|
require 'minitest/autorun'
|
26
|
+
require 'webmock/minitest'
|
26
27
|
require 'jekyll'
|
27
28
|
require_relative '../lib/jekyll-chatgpt-translate/ping'
|
28
29
|
|
@@ -40,8 +41,22 @@ class GptTranslate::PingTest < Minitest::Test
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def test_when_exists
|
44
|
+
stub_request(:any, 'https://www.yegor256.com/about-me.html')
|
43
45
|
site = FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
|
44
46
|
ping = GptTranslate::Ping.new(site, '/about-me.html')
|
45
47
|
assert(!ping.exists?)
|
46
48
|
end
|
49
|
+
|
50
|
+
def test_when_not_exists
|
51
|
+
stub_request(:any, 'https://www.yegor256.com/absent.html').to_return(status: 404)
|
52
|
+
site = FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
|
53
|
+
ping = GptTranslate::Ping.new(site, '/absent.html')
|
54
|
+
assert(!ping.exists?)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_relative_path
|
58
|
+
assert_raises do
|
59
|
+
GptTranslate::Ping.new({}, '404.html')
|
60
|
+
end
|
61
|
+
end
|
47
62
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-chatgpt-translate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: iri
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- lib/jekyll-chatgpt-translate/ping.rb
|
126
126
|
- lib/jekyll-chatgpt-translate/plain.rb
|
127
127
|
- lib/jekyll-chatgpt-translate/version.rb
|
128
|
+
- logo.png
|
128
129
|
- renovate.json
|
129
130
|
- test/test__helper.rb
|
130
131
|
- test/test_chatgpt.rb
|