i18n-ai 0.1.1 → 0.1.4

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: a4b8bb0bd9755345297a83d3c236a95fa9d68c1b432743f08cb48b9bc38fa03b
4
- data.tar.gz: 62f2a9fc7281367224041c3222191e345c15848757ee6e6cfc4905de2dc4156f
3
+ metadata.gz: f98f84d08a1cbe5960d00ca2b76c1d1ce7adc930431247971694b4a93e497367
4
+ data.tar.gz: b42bc703a7bc1880a3503a1470b9f51923ce925ace01458f590e836313f80478
5
5
  SHA512:
6
- metadata.gz: 2a72a92c4f61419e2a76e9ad7df7086168128d24c5d6eafe9be395be4db948e6e6d06937d2db8d11356a85f295c53cf46952fb09eb1c7171eacfe4aa4e009d89
7
- data.tar.gz: 45997aa4a669cc795ee91841a6de94974ba782387ad1e55384700d3a407749b097079abe77aec6c2f505a42bf31adfdb01a145e8cc24dab52b3a7089b860044c
6
+ metadata.gz: 2500f28ee2e4185e89b433a1265b863647a7fbbdd35c1288ecd33e939261cec5ff328875b96ddb531506462914c77bd7cd8d3db6145ebc324fbc80efa321f1a9
7
+ data.tar.gz: e5f6d3da9c17b2fd58d578aa3bd2037e213d9cf8a175df15f9d791bd24ae96e0be5eeb7432e1401d08fa94863dd6be2c94077e133e04375fd33a92fddb6b9ac6
data/.rubocop.yml CHANGED
@@ -6,3 +6,26 @@ Style/StringLiterals:
6
6
 
7
7
  Style/StringLiteralsInInterpolation:
8
8
  EnforcedStyle: double_quotes
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ - 'spec/**/*_spec.rb'
13
+
14
+ Layout/LineLength:
15
+ Exclude:
16
+ - 'spec/**/*_spec.rb'
17
+
18
+ Naming/FileName:
19
+ Exclude:
20
+ - 'lib/i18n-ai.rb'
21
+
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'i18n-ai.gemspec'
25
+
26
+ Style/Documentation:
27
+ Enabled: false
28
+
29
+ Style/BlockDelimiters:
30
+ Exclude:
31
+ - 'spec/**/*_spec.rb'
data/lib/i18n-ai.rb CHANGED
@@ -1,21 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "i18n-ai/version"
4
- require_relative "i18n-ai/railtie"
5
- require_relative "i18n-ai/configuration"
6
-
7
- module I18nAi
8
- class Error < StandardError; end
9
-
10
- class << self
11
- attr_writer :configuration
12
-
13
- def configuration
14
- @configuration ||= Configuration.new
15
- end
16
-
17
- def configure
18
- yield(configuration)
19
- end
20
- end
21
- end
3
+ require_relative "i18n_ai"
@@ -5,39 +5,34 @@ require_relative "base_client"
5
5
 
6
6
  module I18nAi
7
7
  module Clients
8
+ # The AnthropicClient class is responsible for interacting with the Anthropic API
8
9
  class AnthropicClient < BaseClient
9
10
  def initialize
10
11
  super
11
12
  @client = Anthropic::Client.new(
12
- access_token: @config[:access_token]
13
+ access_token: config[:access_token]
13
14
  )
14
15
  end
15
16
 
16
17
  def chat(locale, text)
17
18
  response = @client.messages(
18
19
  parameters: {
19
- model: @config[:model],
20
- messages: [
21
- { role: "user", content: content(locale, text) }
22
- ]
20
+ model: config[:model],
21
+ messages: [{ role: "user", content: chat_prompt(locale, text) }],
22
+ max_tokens: 4096
23
23
  }
24
24
  )
25
-
26
25
  parse_response(response)
27
- rescue StandardError => e
28
- handle_error(e)
29
26
  end
30
27
 
31
28
  private
32
29
 
33
30
  def parse_response(response)
34
31
  response.dig("content", 0, "text")
35
- rescue TypeError, NoMethodError => e
36
- handle_error(e)
37
32
  end
38
33
 
39
- def handle_error(error)
40
- puts "Error: #{error.message}"
34
+ def extract_translated_content(chat_content)
35
+ chat_content
41
36
  end
42
37
  end
43
38
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nAi
4
+ module Clients
5
+ # The BaseClient class serves as a base class for all AI client implementations
6
+ class BaseClient
7
+ attr_reader :config
8
+
9
+ def initialize
10
+ @config = I18nAi.configuration.ai_settings
11
+ end
12
+
13
+ def translate_content(locale, content)
14
+ chat_content = chat(locale, content)
15
+ extract_translated_content(chat_content)
16
+ end
17
+
18
+ private
19
+
20
+ def chat_prompt(locale, text_to_translate)
21
+ <<~PROMPT
22
+ Translate the following YAML content to the language of the country using the ISO 639 language code
23
+ of #{locale}. Make sure to retain the keys in english except the first key which is the 2 letter language code:
24
+
25
+ """"
26
+ #{text_to_translate}"
27
+ """"
28
+
29
+ Return only the YAML content without explanation.
30
+
31
+ Example Return YAML:
32
+
33
+ """
34
+ {{ISO_639 language code}}:
35
+ key_1: "value1"
36
+ """
37
+ PROMPT
38
+ end
39
+
40
+ def chat(locale, text)
41
+ raise NotImplementedError, "Subclasses must implement this method"
42
+ end
43
+
44
+ def parse_response(response)
45
+ raise NotImplementedError, "Subclasses must implement this method"
46
+ end
47
+
48
+ def extract_translated_content(chat_content)
49
+ raise NotImplementedError, "Subclasses must implement this method"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -9,7 +9,7 @@ module I18nAi
9
9
  def initialize
10
10
  super
11
11
  @client = OpenAI::Client.new(
12
- access_token: @config[:access_token],
12
+ access_token: config[:access_token],
13
13
  log_errors: true
14
14
  )
15
15
  end
@@ -18,14 +18,11 @@ module I18nAi
18
18
  response = @client.chat(
19
19
  parameters: {
20
20
  model: @config[:model],
21
- messages: [{ role: "user", content: content(locale, text) }],
22
- max_tokens: 5000
21
+ messages: [{ role: "user", content: chat_prompt(locale, text) }],
22
+ max_tokens: 4096
23
23
  }
24
24
  )
25
-
26
25
  parse_response(response)
27
- rescue StandardError => e
28
- handle_error(e)
29
26
  end
30
27
 
31
28
  private
@@ -39,6 +36,11 @@ module I18nAi
39
36
  def handle_error(error)
40
37
  puts "Error: #{error.message}"
41
38
  end
39
+
40
+ def extract_translated_content(chat_content)
41
+ match_data = chat_content.match(/```yaml(.*?)```/m)
42
+ match_data ? match_data[1].strip : nil
43
+ end
42
44
  end
43
45
  end
44
46
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nAi
4
+ # Configuration class for I18nAi
4
5
  class Configuration
5
6
  attr_accessor :ai_settings, :generate_locales
6
7
 
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+ require "digest"
5
+
6
+ require_relative "clients/open_ai_client"
7
+ require_relative "clients/anthropic_client"
8
+
9
+ module I18nAi
10
+ # The Railtie class provides a way to integrate I18nAi into an application
11
+ class Railtie < Rails::Railtie
12
+ # Initialize the I18nAi middleware
13
+ class I18nAiMiddleware
14
+ attr_reader :client
15
+
16
+ def initialize(app)
17
+ @app = app
18
+ @client = configure_client
19
+ @last_checksum = nil
20
+ end
21
+
22
+ def call(env)
23
+ locales_file = locate_locales_file
24
+
25
+ if file_exists?(locales_file)
26
+ process_locales_file(locales_file)
27
+ else
28
+ log_file_not_found
29
+ end
30
+
31
+ @app.call(env)
32
+ end
33
+
34
+ private
35
+
36
+ def locate_locales_file
37
+ Rails.root.join("config", "locales", "en.yml")
38
+ end
39
+
40
+ def file_exists?(file)
41
+ File.exist?(file)
42
+ end
43
+
44
+ def process_locales_file(file)
45
+ current_checksum = calculate_checksum(file)
46
+ log_checksum(current_checksum)
47
+
48
+ first_load = @last_checksum.nil?
49
+ file_changed = current_checksum != @last_checksum
50
+ log_generate_status(first_load, file_changed)
51
+
52
+ return unless first_load || file_changed
53
+
54
+ @last_checksum = current_checksum
55
+ generate_translations(file)
56
+ end
57
+
58
+ def log_checksum(checksum)
59
+ puts "==> en.yml checksum: #{checksum}"
60
+ end
61
+
62
+ def log_generate_status(first_load, file_changed)
63
+ puts "==> en.yml generate: #{first_load || file_changed}"
64
+ end
65
+
66
+ def log_file_not_found
67
+ puts "en.yml file not found"
68
+ end
69
+
70
+ def configure_client
71
+ config = I18nAi.configuration.ai_settings
72
+ case config[:provider]
73
+ when "anthropic"
74
+ I18nAi::Clients::AnthropicClient.new
75
+ when "openai"
76
+ I18nAi::Clients::OpenAiClient.new
77
+ else
78
+ raise "Unknown AI provider: #{config[:provider]}"
79
+ end
80
+ end
81
+
82
+ def generate_translations(locales_file)
83
+ locales = load_locales(locales_file)
84
+ text_to_translate = locales.to_yaml
85
+ generate_locales = I18nAi.configuration.generate_locales
86
+
87
+ generate_locales.each do |locale|
88
+ translated_content = client.translate_content(locale, text_to_translate)
89
+ save_translated_locales(locale, translated_content) if translated_content
90
+ end
91
+ end
92
+
93
+ def load_locales(locales_file)
94
+ YAML.load_file(locales_file)
95
+ end
96
+
97
+ def save_translated_locales(locale, translated_content)
98
+ locales_file = Rails.root.join("config", "locales", "#{locale}.yml")
99
+ File.write(locales_file, translated_content)
100
+ end
101
+
102
+ def calculate_checksum(file_path)
103
+ Digest::SHA256.file(file_path).hexdigest
104
+ end
105
+ end
106
+
107
+ initializer "i18n_ai.configure_middleware", before: :build_middleware_stack do |app|
108
+ app.middleware.use I18nAiMiddleware
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nAi
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/i18n_ai.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "i18n_ai/version"
4
+ require_relative "i18n_ai/railtie"
5
+ require_relative "i18n_ai/configuration"
6
+
7
+ # The I18nAi module provides functionality for integrating AI-based translation services
8
+ module I18nAi
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ attr_writer :configuration
13
+
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield(configuration)
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Estoque
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-05 00:00:00.000000000 Z
11
+ date: 2024-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8'
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: anthropic
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +58,34 @@ dependencies:
38
58
  - - ">="
39
59
  - !ruby/object:Gem::Version
40
60
  version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: vcr
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
41
89
  description:
42
90
  email:
43
91
  - william.estoque@gmail.com
@@ -52,13 +100,13 @@ files:
52
100
  - README.md
53
101
  - Rakefile
54
102
  - lib/i18n-ai.rb
55
- - lib/i18n-ai/clients/anthropic_client.rb
56
- - lib/i18n-ai/clients/base_client.rb
57
- - lib/i18n-ai/clients/open_ai_client.rb
58
- - lib/i18n-ai/configuration.rb
59
- - lib/i18n-ai/railtie.rb
60
- - lib/i18n-ai/version.rb
61
- - sig/i18n/ai.rbs
103
+ - lib/i18n_ai.rb
104
+ - lib/i18n_ai/clients/anthropic_client.rb
105
+ - lib/i18n_ai/clients/base_client.rb
106
+ - lib/i18n_ai/clients/open_ai_client.rb
107
+ - lib/i18n_ai/configuration.rb
108
+ - lib/i18n_ai/railtie.rb
109
+ - lib/i18n_ai/version.rb
62
110
  homepage: https://github.com/narralabs/i18n-ai
63
111
  licenses:
64
112
  - MIT
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module I18nAi
4
- module Clients
5
- class BaseClient
6
- def initialize
7
- @config = I18nAi.configuration.ai_settings
8
- end
9
-
10
- def content(locale, text_to_translate)
11
- "Translate the following YAML content to #{locale.to_s.upcase} and make sure to retain the keys in english except the first key which is the 2 letter language code:\n\n#{text_to_translate}"
12
- end
13
-
14
- def parse_response(response)
15
- raise NotImplementedError, "Subclasses must implement this method"
16
- end
17
- end
18
- end
19
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/railtie"
4
- require "digest"
5
-
6
- require_relative "clients/open_ai_client"
7
- require_relative "clients/anthropic_client"
8
-
9
- module I18nAi
10
- class Railtie < Rails::Railtie
11
- class I18nAiMiddleware
12
- def initialize(app)
13
- @app = app
14
- @client = configure_client
15
- @last_checksum = nil
16
- end
17
-
18
- def call(env)
19
- locales_file = Rails.root.join("config", "locales", "en.yml")
20
-
21
- if File.exist?(locales_file)
22
- current_checksum = calculate_checksum(locales_file)
23
- puts "==> en.yml checksum: #{current_checksum}"
24
- first_load = @last_checksum.nil?
25
- file_changed = current_checksum != @last_checksum
26
- puts "==> en.yml generate: #{first_load || file_changed}"
27
-
28
- if first_load || file_changed
29
- @last_checksum = current_checksum
30
- generate_translations(locales_file)
31
- end
32
- else
33
- puts "en.yml file not found"
34
- end
35
-
36
- @app.call(env)
37
- end
38
-
39
- private
40
-
41
- def configure_client
42
- config = I18nAi.configuration.ai_settings
43
- case config[:provider]
44
- when "anthropic"
45
- I18nAi::Clients::AnthropicClient.new
46
- when "openai"
47
- I18nAi::Clients::OpenAiClient.new
48
- else
49
- raise "Unknown AI provider: #{config[:provider]}"
50
- end
51
- end
52
-
53
- def generate_translations(locales_file)
54
- locales = YAML.load_file(locales_file)
55
- text_to_translate = locales.to_yaml
56
- generate_locales = I18nAi.configuration.generate_locales
57
-
58
- generate_locales.each do |locale|
59
- # Make a request to OpenAI to translate the locales to the specified locale
60
- response = @client.chat(
61
- locale, text_to_translate
62
- )
63
-
64
- next unless response
65
-
66
- match_data = response.match(/```yaml(.*?)```/m)
67
- str = match_data ? match_data[1].strip : nil
68
-
69
- # Save the response to <locale>.yml
70
- locales_file = Rails.root.join("config", "locales", "#{locale}.yml")
71
- File.write(locales_file, str)
72
- end
73
- end
74
-
75
- def calculate_checksum(file_path)
76
- Digest::SHA256.file(file_path).hexdigest
77
- end
78
- end
79
-
80
- initializer "i18n_ai.configure_middleware", before: :build_middleware_stack do |app|
81
- app.middleware.use I18nAiMiddleware
82
- end
83
- end
84
- end
data/sig/i18n/ai.rbs DELETED
@@ -1,6 +0,0 @@
1
- module I18n
2
- module Ai
3
- VERSION: String
4
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
- end
6
- end