i18n-ai 0.1.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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