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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b2375ac0c0ab9bc728d9e1d6684b4cb1f1424ae5718095a8644a447b91487e8
4
- data.tar.gz: bf13ed8a50dad4ca6f4cdd1d719728a57e0c7081a16eda4d56bb6dbbcae5bb3d
3
+ metadata.gz: ae5a29abd8e4cb757fa40d7b50c999a4453901b1be9a597f23e560fa3900416d
4
+ data.tar.gz: e1769e8544c657ad086c462a1d32652edd434ac26509b064d1441f8beb35ea28
5
5
  SHA512:
6
- metadata.gz: 26dbc8403f36f4bfb8c127efac666dd8884823be38b9fda7a7c92800c50f6805cfe7aa8b04251830412025ae3582a799331fdff0e8336507a6c2004fbbbbf49c
7
- data.tar.gz: 5f5cd81fd192d7945f57b23148df57ebd95f09ff8b917190ffd89cb2a8dc4d44f49afffc78d7032c0194a6a0e0aa25ce95b80d97a210cfd1cbfe9965e0a44f6c
6
+ metadata.gz: 97ac19fd067d197faf5fb5c2b541a0641b56c0ca0fbbef3f9fbfe02c4efacb89657fc9d9011bd10081ce6221807eba792be5ed8f12e9617a197c36574f668df4
7
+ data.tar.gz: 2f5251871b78f3052f33b7c1778318d89945b9874295fc36da9ae8f8d7fb34a720bb475210f0e1546ccf18c7a136ee2ed5e24d70aaa4630d5fe7e1f707d95bac
data/.rubocop.yml CHANGED
@@ -27,3 +27,9 @@ Layout/EmptyLineAfterGuardClause:
27
27
  Enabled: false
28
28
  Naming/FileName:
29
29
  Enabled: false
30
+ Layout/IndentationWidth:
31
+ Enabled: false
32
+ Layout/ElseAlignment:
33
+ Enabled: false
34
+ Layout/EndAlignment:
35
+ Enabled: false
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.55.1', require: false
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
  [![rake](https://github.com/yegor256/jekyll-chatgpt-translate/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/jekyll-chatgpt-translate/actions/workflows/rake.yml)
2
4
  [![Gem Version](https://badge.fury.io/rb/jekyll-chatgpt-translate.svg)](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-:title-chinese.html
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
- The plugin is compatible with
38
- [Jekyll 3.9.3](https://jekyllrb.com/news/2023/01/29/jekyll-3-9-3-released/) and
39
- [Jekyll 4.3.2](https://jekyllrb.com/news/2023/01/20/jekyll-4-3-2-released/).
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
- Test is like this:
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-:title-chinese.html
16
+ permalink: :year-:month-:day-:slug-chinese.html
17
17
  layout: chinese-translated
18
18
  -
19
19
  language: fr
20
- permalink: :year/:title-french.html
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-Hello-chinese.html" exists
40
- Then File "_site/2023/Hello-french.html" exists
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.6'
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
- if t.nil?
88
- Jekyll.logger.error("Failed to translate #{par.split.count}
89
- #{@source.upcase} words after #{attempt} attempts :(")
90
- 'FAILED TO TRANSLATE THIS PARAGRAPH'
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
- @site = site
46
- key = ENV.fetch('OPENAI_API_KEY', nil)
47
- if key.nil? && Jekyll.env == 'development'
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']).to_s
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 = "_chatgpt-translated/#{doc.basename}"
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 generated in #{(Time.now - start).round(2)}s")
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
- # Returns the plugin's config or an empty hash if not set
107
- def config
108
- @config ||= @site.config['chatgpt-translate'] || {}
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 to_s
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, link)
43
+ def initialize(site, path)
44
44
  @site = site
45
- @link = link
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(@link)
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
@@ -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
- '/:year.html'
39
- ).to_s
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
- '/:year-:title.html'
49
- ).to_s
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.6
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-20 00:00:00.000000000 Z
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