jekyll-chatgpt-translate 0.0.5 → 0.0.7
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/Gemfile +1 -0
- data/features/cli.feature +6 -4
- data/features/step_definitions/steps.rb +1 -1
- data/jekyll-chatgpt-translate.gemspec +1 -1
- data/lib/jekyll-chatgpt-translate/chatgpt.rb +9 -14
- data/lib/jekyll-chatgpt-translate/generator.rb +8 -15
- data/lib/jekyll-chatgpt-translate/permalink.rb +51 -0
- data/lib/jekyll-chatgpt-translate/ping.rb +4 -3
- data/test/test_chatgpt.rb +8 -0
- data/test/test_permalink.rb +52 -0
- data/test/test_ping.rb +15 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 951ecc1df96633e4552be477e42c43977b0187282978d9cb1c07bedc3913e8fa
|
4
|
+
data.tar.gz: 9fb20f291797eaee903f0ca20aaf0adcc7ed65fd77626dda6afbe3c6390418cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d94a2be708d58bb23427a707660276ce7d42dc90372924644b76558933c631834d3eeece4b1bb46c5b89221c3d758b62e71958137e355edb02451ef671fc9f03
|
7
|
+
data.tar.gz: 623f0e383d89805288678b37d9304d365b3adf6995d60c9bec4e7a9e6c85e2be44cb51e4a1001892cb5919a55799f99dfe9e174c5edf931f9f2e641d24ffed37
|
data/Gemfile
CHANGED
data/features/cli.feature
CHANGED
@@ -13,11 +13,11 @@ 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
|
"""
|
@@ -30,13 +30,15 @@ Feature: Simple site building
|
|
30
30
|
And I have a "_posts/2023-01-01-hello.md" file with content:
|
31
31
|
"""
|
32
32
|
---
|
33
|
+
title: Hello, world!
|
33
34
|
layout: default
|
34
35
|
---
|
35
36
|
Hello, world!
|
36
37
|
"""
|
37
38
|
Then I build Jekyll site
|
39
|
+
Then File "_chatgpt-translated/cn/2023-01-01-hello-cn.md" exists
|
38
40
|
Then File "_site/2023/01/01/hello.html" exists
|
39
|
-
Then File "_site/2023-01-01-
|
40
|
-
Then File "_site/2023/
|
41
|
+
Then File "_site/2023-01-01-hello-chinese.html" exists
|
42
|
+
Then File "_site/2023/hello-french.html" exists
|
41
43
|
And Exit code is zero
|
42
44
|
|
@@ -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.7'
|
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
|
[
|
@@ -24,6 +24,7 @@
|
|
24
24
|
|
25
25
|
require 'jekyll'
|
26
26
|
require_relative 'chatgpt'
|
27
|
+
require_relative 'permalink'
|
27
28
|
require_relative 'ping'
|
28
29
|
require_relative 'plain'
|
29
30
|
require_relative 'version'
|
@@ -42,11 +43,12 @@ class GptTranslate::Generator < Jekyll::Generator
|
|
42
43
|
# Main plugin action, called by Jekyll-core
|
43
44
|
def generate(site)
|
44
45
|
@site = site
|
46
|
+
home = '_chatgpt-translated'
|
45
47
|
key = ENV.fetch('OPENAI_API_KEY', nil)
|
46
48
|
if key.nil? && Jekyll.env == 'development'
|
47
|
-
Jekyll.logger.info(
|
48
|
-
we are in development mode, no actual translation will happen,
|
49
|
-
but pages will be generated
|
49
|
+
Jekyll.logger.info("OPENAI_API_KEY environment variable is not set and \
|
50
|
+
we are in development mode, no actual translation will happen, \
|
51
|
+
but pages will be generated")
|
50
52
|
key = ''
|
51
53
|
end
|
52
54
|
if key.nil?
|
@@ -60,7 +62,7 @@ but pages will be generated')
|
|
60
62
|
site.posts.docs.each do |doc|
|
61
63
|
plain = GptTranslate::Plain.new(doc.content).to_s
|
62
64
|
config['targets'].each do |target|
|
63
|
-
link =
|
65
|
+
link = GptTranslate::Permalink.new(doc, target['permalink']).to_path
|
64
66
|
next if GptTranslate::Ping.new(site, link).exists?
|
65
67
|
lang = target['language']
|
66
68
|
raise 'Language must be defined for each target' if target.nil?
|
@@ -76,7 +78,7 @@ but pages will be generated')
|
|
76
78
|
lang
|
77
79
|
)
|
78
80
|
translated = gpt.translate(plain)
|
79
|
-
path =
|
81
|
+
path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
|
80
82
|
FileUtils.mkdir_p(File.dirname(path))
|
81
83
|
File.write(
|
82
84
|
path,
|
@@ -97,7 +99,7 @@ but pages will be generated')
|
|
97
99
|
end
|
98
100
|
break if total >= threshold
|
99
101
|
end
|
100
|
-
Jekyll.logger.info("#{total} pages
|
102
|
+
Jekyll.logger.info("#{total} pages translated in #{(Time.now - start).round(2)}s")
|
101
103
|
end
|
102
104
|
|
103
105
|
private
|
@@ -106,13 +108,4 @@ but pages will be generated')
|
|
106
108
|
def config
|
107
109
|
@config ||= @site.config['chatgpt-translate'] || {}
|
108
110
|
end
|
109
|
-
|
110
|
-
def permalink(doc, template)
|
111
|
-
raise 'permalink must be defined for each target' if template.nil?
|
112
|
-
template
|
113
|
-
.gsub(':year', format('%04d', doc['date'].year))
|
114
|
-
.gsub(':month', format('%02d', doc['date'].month))
|
115
|
-
.gsub(':day', format('%02d', doc['date'].day))
|
116
|
-
.gsub(':title', doc['title'])
|
117
|
-
end
|
118
111
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# (The MIT License)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2023 Yegor Bugayenko
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
|
25
|
+
require 'cgi'
|
26
|
+
|
27
|
+
# The module we are in.
|
28
|
+
module GptTranslate; end
|
29
|
+
|
30
|
+
# Permalink.
|
31
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
32
|
+
# Copyright:: Copyright (c) 2023 Yegor Bugayenko
|
33
|
+
# License:: MIT
|
34
|
+
class GptTranslate::Permalink
|
35
|
+
def initialize(doc, template)
|
36
|
+
@doc = doc
|
37
|
+
raise 'permalink must be defined for each target' if template.nil?
|
38
|
+
@template = template
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_path
|
42
|
+
path = @template
|
43
|
+
.gsub(':year', format('%04d', @doc['date'].year))
|
44
|
+
.gsub(':month', format('%02d', @doc['date'].month))
|
45
|
+
.gsub(':day', format('%02d', @doc['date'].day))
|
46
|
+
.gsub(':title', CGI.escape(@doc['title']))
|
47
|
+
.gsub(':slug', CGI.escape(@doc['slug']))
|
48
|
+
path = "/#{path}" unless path.start_with?('/')
|
49
|
+
path
|
50
|
+
end
|
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/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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# (The MIT License)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2023 Yegor Bugayenko
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require_relative '../lib/jekyll-chatgpt-translate/permalink'
|
27
|
+
|
28
|
+
# Permalink test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2023 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class GptTranslate::PermalinkTest < Minitest::Test
|
33
|
+
def test_simple_link
|
34
|
+
assert_equal(
|
35
|
+
'/2023.html',
|
36
|
+
GptTranslate::Permalink.new(
|
37
|
+
{ 'date' => Time.parse('2023-01-01'), 'title' => 'Hello', 'slug' => 'hello' },
|
38
|
+
':year.html'
|
39
|
+
).to_path
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_unicode_link
|
44
|
+
assert_equal(
|
45
|
+
'/2023-%23%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82.html',
|
46
|
+
GptTranslate::Permalink.new(
|
47
|
+
{ 'date' => Time.parse('2023-01-01'), 'title' => '#привет', 'slug' => 'hello' },
|
48
|
+
':year-:title.html'
|
49
|
+
).to_path
|
50
|
+
)
|
51
|
+
end
|
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.7
|
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
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- lib/jekyll-chatgpt-translate.rb
|
122
122
|
- lib/jekyll-chatgpt-translate/chatgpt.rb
|
123
123
|
- lib/jekyll-chatgpt-translate/generator.rb
|
124
|
+
- lib/jekyll-chatgpt-translate/permalink.rb
|
124
125
|
- lib/jekyll-chatgpt-translate/ping.rb
|
125
126
|
- lib/jekyll-chatgpt-translate/plain.rb
|
126
127
|
- lib/jekyll-chatgpt-translate/version.rb
|
@@ -128,6 +129,7 @@ files:
|
|
128
129
|
- test/test__helper.rb
|
129
130
|
- test/test_chatgpt.rb
|
130
131
|
- test/test_generator.rb
|
132
|
+
- test/test_permalink.rb
|
131
133
|
- test/test_ping.rb
|
132
134
|
- test/test_plain.rb
|
133
135
|
homepage: https://github.com/yegor256/jekyll-chatgpt-translate
|