i18n-ai 0.1.0 → 0.1.3

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: 06e2c7c1e6b90add5766612403fefc33653d55b98e2a49b6ba0920a736545788
4
- data.tar.gz: 19c739baa0b768bb12496cbb43dc2e58eb78a6afe629ab2db11e53dcfe5e94b0
3
+ metadata.gz: 91c87df3579aa11f253f670664cbcf4721dc4145cb12740ab05f9f81a8fef913
4
+ data.tar.gz: 590b0626a6e5dee174d34ddd429154485a7345539bc398815851ca5c4bd50ea3
5
5
  SHA512:
6
- metadata.gz: a3abb0c81d6716e37df291fa0ef3b2b3853d97696de0247e0677b453cb781d154751b4dd8c4577596ef2e02ef35630a42956189547df3a046e556c85fc062764
7
- data.tar.gz: 971b820565e02c742653a423e5eef1f8f6b4111cb3d9d2e535b0752fceb4c1996fc5e09338d866d7a19c2a9e9bd95299c110e40f8f6d0d17a2ffd26ef1c3154f
6
+ metadata.gz: '08ecd1e198abe8f8363ca6bc227a36a00335f6cc52024b7c5f6ef2013e6a0205a860bdcbc102c34b7953b0708d7f02bc7379946deb7f0b2e702314ffd3f6a143'
7
+ data.tar.gz: 4e3b1a31c6112457ed53f274d32cf98c21bae53bffcc69d8b91e2ec79403790a4090fbb69a37bc4e7cdd2f30ad99a232777160e29c0944750906c4b1d477483b
data/.rubocop.yml CHANGED
@@ -6,3 +6,15 @@ 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'
data/README.md CHANGED
@@ -14,19 +14,47 @@ gem "i18n-ai"
14
14
 
15
15
  And do `bundle install`.
16
16
 
17
- ## Usage
17
+ ## Configuration
18
18
 
19
- First you need to set the environment variable `ENV["OPENAI_ACCESS_TOKEN"]`.
19
+ To use I18nAi, you need to set the appropriate environment variables based on the AI service you wish to use.
20
20
 
21
- To configure and enable other locales, create a file `config/initializers/i18n_ai.rb` and add the following:
21
+ ### OpenAi
22
+
23
+ If you are using OpenAI, set `ENV["OPENAI_ACCESS_TOKEN"]`
24
+
25
+ To configure and enable other locales, create a file `config/initializers/i18n_aii.rb` and add the following:
22
26
 
23
27
  ```
24
28
  # config/initializers/i18n_ai.rb
25
29
  I18nAi.configure do |config|
30
+ config.ai_settings = {
31
+ provider: "openai",
32
+ model: "gpt-4o-mini",
33
+ access_token: ENV["OPENAI_ACCESS_TOKEN"]
34
+ }
26
35
  config.generate_locales = [:es, :it] # add your other supported locales to this array
27
36
  end
28
37
  ```
29
38
 
39
+ ### Anthropic's Claude
40
+
41
+ If you prefer to use Anthropic's Claude, set `ENV[ANTHROPIC_API_KEY]`
42
+
43
+ To configure and enable other locales, create a file `config/initializers/i18n_ai.rb` and add the following:
44
+
45
+ ```
46
+ I18nAi.configure do |config|
47
+ config.ai_settings = {
48
+ provider: "anthropic",
49
+ model: "claude-3-haiku-20240307",
50
+ access_token: ENV["ANTHROPIC_ACCESS_TOKEN"]
51
+ }
52
+ config.generate_locales = [:es]
53
+ end
54
+ ```
55
+
56
+ ## Usage
57
+
30
58
  Every page reload, the gem will check if the `en.yml` file changed and if it did, it will automatically generate the configured locale files.
31
59
 
32
60
  The gem has been setup to generate a `es.yml` file by default.
data/lib/i18n-ai.rb CHANGED
@@ -1,19 +1,3 @@
1
- require_relative "i18n-ai/version"
2
- require_relative "i18n-ai/railtie"
3
- require_relative "i18n-ai/configuration"
1
+ # frozen_string_literal: true
4
2
 
5
- module I18nAi
6
- class Error < StandardError; end
7
-
8
- class << self
9
- attr_writer :configuration
10
-
11
- def configuration
12
- @configuration ||= Configuration.new
13
- end
14
-
15
- def configure
16
- yield(configuration)
17
- end
18
- end
19
- end
3
+ require_relative "i18n_ai"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anthropic"
4
+ require_relative "base_client"
5
+
6
+ module I18nAi
7
+ module Clients
8
+ # The AnthropicClient class is responsible for interacting with the Anthropic API
9
+ class AnthropicClient < BaseClient
10
+ def initialize
11
+ super
12
+ @client = Anthropic::Client.new(
13
+ access_token: @config[:access_token]
14
+ )
15
+ end
16
+
17
+ def chat(locale, text)
18
+ response = @client.messages(
19
+ parameters: {
20
+ model: @config[:model],
21
+ messages: [{ role: "user", content: content(locale, text) }]
22
+ }
23
+ )
24
+
25
+ parse_response(response)
26
+ rescue StandardError => e
27
+ handle_error(e)
28
+ end
29
+
30
+ private
31
+
32
+ def parse_response(response)
33
+ response.dig("content", 0, "text")
34
+ rescue TypeError, NoMethodError => e
35
+ handle_error(e)
36
+ end
37
+
38
+ def handle_error(error)
39
+ puts "Error: #{error.message}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
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
+ def initialize
8
+ @config = I18nAi.configuration.ai_settings
9
+ end
10
+
11
+ def content(locale, text_to_translate)
12
+ # rubocop:disable Layout/LineLength
13
+ "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}"
14
+ # rubocop:enable Layout/LineLength
15
+ end
16
+
17
+ def parse_response(response)
18
+ raise NotImplementedError, "Subclasses must implement this method"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openai"
4
+ require_relative "base_client"
5
+
6
+ module I18nAi
7
+ module Clients
8
+ # The AnthropicClient class is responsible for interacting with the OpenAI API
9
+ class OpenAiClient < BaseClient
10
+ def initialize
11
+ super
12
+ @client = OpenAI::Client.new(
13
+ access_token: @config[:access_token],
14
+ log_errors: true
15
+ )
16
+ end
17
+
18
+ def chat(locale, text)
19
+ response = @client.chat(
20
+ parameters: {
21
+ model: @config[:model],
22
+ messages: [{ role: "user", content: content(locale, text) }],
23
+ max_tokens: 5000
24
+ }
25
+ )
26
+
27
+ parse_response(response)
28
+ rescue StandardError => e
29
+ handle_error(e)
30
+ end
31
+
32
+ private
33
+
34
+ def parse_response(response)
35
+ response.dig("choices", 0, "message", "content")
36
+ rescue TypeError, NoMethodError => e
37
+ handle_error(e)
38
+ end
39
+
40
+ def handle_error(error)
41
+ puts "Error: #{error.message}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nAi
4
+ # Configuration class for I18nAi
5
+ class Configuration
6
+ attr_accessor :ai_settings, :generate_locales
7
+
8
+ def initialize
9
+ @ai_settings = {}
10
+ @generate_locales = [:es]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,121 @@
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
+ def initialize(app)
15
+ @app = app
16
+ @client = configure_client
17
+ @last_checksum = nil
18
+ end
19
+
20
+ def call(env)
21
+ locales_file = locate_locales_file
22
+
23
+ if file_exists?(locales_file)
24
+ process_locales_file(locales_file)
25
+ else
26
+ log_file_not_found
27
+ end
28
+
29
+ @app.call(env)
30
+ end
31
+
32
+ private
33
+
34
+ def locate_locales_file
35
+ Rails.root.join("config", "locales", "en.yml")
36
+ end
37
+
38
+ def file_exists?(file)
39
+ File.exist?(file)
40
+ end
41
+
42
+ def process_locales_file(file)
43
+ current_checksum = calculate_checksum(file)
44
+ log_checksum(current_checksum)
45
+
46
+ first_load = @last_checksum.nil?
47
+ file_changed = current_checksum != @last_checksum
48
+ log_generate_status(first_load, file_changed)
49
+
50
+ return unless first_load || file_changed
51
+
52
+ @last_checksum = current_checksum
53
+ generate_translations(file)
54
+ end
55
+
56
+ def log_checksum(checksum)
57
+ puts "==> en.yml checksum: #{checksum}"
58
+ end
59
+
60
+ def log_generate_status(first_load, file_changed)
61
+ puts "==> en.yml generate: #{first_load || file_changed}"
62
+ end
63
+
64
+ def log_file_not_found
65
+ puts "en.yml file not found"
66
+ end
67
+
68
+ def configure_client
69
+ config = I18nAi.configuration.ai_settings
70
+ case config[:provider]
71
+ when "anthropic"
72
+ I18nAi::Clients::AnthropicClient.new
73
+ when "openai"
74
+ I18nAi::Clients::OpenAiClient.new
75
+ else
76
+ raise "Unknown AI provider: #{config[:provider]}"
77
+ end
78
+ end
79
+
80
+ def generate_translations(locales_file)
81
+ locales = load_locales(locales_file)
82
+ text_to_translate = locales.to_yaml
83
+ generate_locales = I18nAi.configuration.generate_locales
84
+
85
+ generate_locales.each do |locale|
86
+ response = translate_locales(locale, text_to_translate)
87
+ next unless response
88
+
89
+ translated_content = extract_translated_content(response)
90
+ save_translated_locales(locale, translated_content) if translated_content
91
+ end
92
+ end
93
+
94
+ def load_locales(locales_file)
95
+ YAML.load_file(locales_file)
96
+ end
97
+
98
+ def translate_locales(locale, text_to_translate)
99
+ @client.chat(locale, text_to_translate)
100
+ end
101
+
102
+ def extract_translated_content(response)
103
+ match_data = response.match(/```yaml(.*?)```/m)
104
+ match_data ? match_data[1].strip : nil
105
+ end
106
+
107
+ def save_translated_locales(locale, translated_content)
108
+ locales_file = Rails.root.join("config", "locales", "#{locale}.yml")
109
+ File.write(locales_file, translated_content)
110
+ end
111
+
112
+ def calculate_checksum(file_path)
113
+ Digest::SHA256.file(file_path).hexdigest
114
+ end
115
+ end
116
+
117
+ initializer "i18n_ai.configure_middleware", before: :build_middleware_stack do |app|
118
+ app.middleware.use I18nAiMiddleware
119
+ end
120
+ end
121
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nAi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.3"
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,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.3
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-01 00:00:00.000000000 Z
11
+ date: 2024-08-06 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'
33
+ - !ruby/object:Gem::Dependency
34
+ name: anthropic
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
13
47
  - !ruby/object:Gem::Dependency
14
48
  name: ruby-openai
15
49
  requirement: !ruby/object:Gem::Requirement
@@ -38,10 +72,13 @@ files:
38
72
  - README.md
39
73
  - Rakefile
40
74
  - lib/i18n-ai.rb
41
- - lib/i18n-ai/configuration.rb
42
- - lib/i18n-ai/railtie.rb
43
- - lib/i18n-ai/version.rb
44
- - sig/i18n/ai.rbs
75
+ - lib/i18n_ai.rb
76
+ - lib/i18n_ai/clients/anthropic_client.rb
77
+ - lib/i18n_ai/clients/base_client.rb
78
+ - lib/i18n_ai/clients/open_ai_client.rb
79
+ - lib/i18n_ai/configuration.rb
80
+ - lib/i18n_ai/railtie.rb
81
+ - lib/i18n_ai/version.rb
45
82
  homepage: https://github.com/narralabs/i18n-ai
46
83
  licenses:
47
84
  - MIT
@@ -1,9 +0,0 @@
1
- module I18nAi
2
- class Configuration
3
- attr_accessor :generate_locales
4
-
5
- def initialize
6
- @generate_locales = [:es]
7
- end
8
- end
9
- end
@@ -1,75 +0,0 @@
1
- require "rails/railtie"
2
- require "openai"
3
- require "digest"
4
-
5
- module I18nAi
6
- class Railtie < Rails::Railtie
7
- class I18nAiMiddleware
8
- def initialize(app)
9
- @app = app
10
- @client = OpenAI::Client.new(
11
- access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"),
12
- log_errors: true
13
- )
14
- @last_checksum = nil
15
- end
16
-
17
- def call(env)
18
- locales_file = Rails.root.join("config", "locales", "en.yml")
19
-
20
- if File.exist?(locales_file)
21
- current_checksum = calculate_checksum(locales_file)
22
- puts "==> en.yml checksum: #{current_checksum}"
23
- first_load = @last_checksum.nil?
24
- file_changed = current_checksum != @last_checksum
25
- puts "==> en.yml generate: #{first_load || file_changed}"
26
-
27
- if first_load || file_changed
28
- @last_checksum = current_checksum
29
- generate_translations(locales_file)
30
- end
31
- else
32
- puts "en.yml file not found"
33
- end
34
-
35
- @app.call(env)
36
- end
37
-
38
- private
39
-
40
- def generate_translations(locales_file)
41
- locales = YAML.load_file(locales_file)
42
- text_to_translate = locales.to_yaml
43
- generate_locales = I18nAi.configuration.generate_locales
44
-
45
- generate_locales.each do |locale|
46
- # Make a request to OpenAI to translate the locales to the specified locale
47
- response = @client.chat(
48
- parameters: {
49
- model: "gpt-4o-mini",
50
- messages: [{ role: "user",
51
- content: "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}" }],
52
- max_tokens: 5000
53
- }
54
- )
55
-
56
- translated_text = response["choices"][0]["message"]["content"]
57
- match_data = translated_text.match(/```yaml(.*?)```/m)
58
- str = match_data ? match_data[1].strip : nil
59
-
60
- # Save the response to <locale>.yml
61
- locales_file = Rails.root.join("config", "locales", "#{locale}.yml")
62
- File.write(locales_file, str)
63
- end
64
- end
65
-
66
- def calculate_checksum(file_path)
67
- Digest::SHA256.file(file_path).hexdigest
68
- end
69
- end
70
-
71
- initializer "i18n_ai.configure_middleware", before: :build_middleware_stack do |app|
72
- app.middleware.use I18nAiMiddleware
73
- end
74
- end
75
- 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