polylingo_chat 0.1.0

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +432 -0
  3. data/lib/generators/polylingo_chat/install/install_generator.rb +107 -0
  4. data/lib/generators/polylingo_chat/install/templates/README +48 -0
  5. data/lib/generators/polylingo_chat/install/templates/channels/application_cable/channel.rb +4 -0
  6. data/lib/generators/polylingo_chat/install/templates/channels/application_cable/connection.rb +30 -0
  7. data/lib/generators/polylingo_chat/install/templates/channels/polylingo_chat_channel.rb +15 -0
  8. data/lib/generators/polylingo_chat/install/templates/create_conversations.rb +9 -0
  9. data/lib/generators/polylingo_chat/install/templates/create_messages.rb +13 -0
  10. data/lib/generators/polylingo_chat/install/templates/create_participants.rb +12 -0
  11. data/lib/generators/polylingo_chat/install/templates/initializer.rb +24 -0
  12. data/lib/generators/polylingo_chat/install/templates/javascript/channels/consumer.js +15 -0
  13. data/lib/generators/polylingo_chat/install/templates/javascript/channels/index.js +2 -0
  14. data/lib/generators/polylingo_chat/install/templates/javascript/chat.js +86 -0
  15. data/lib/generators/polylingo_chat/install/templates/models/conversation.rb +7 -0
  16. data/lib/generators/polylingo_chat/install/templates/models/message.rb +14 -0
  17. data/lib/generators/polylingo_chat/install/templates/models/participant.rb +6 -0
  18. data/lib/generators/polylingo_chat/install_generator.rb +38 -0
  19. data/lib/generators/polylingo_chat/templates/INSTALL_README.md +124 -0
  20. data/lib/generators/polylingo_chat/templates/chat_channel_example.js +18 -0
  21. data/lib/generators/polylingo_chat/templates/create_polyglot_conversations.rb +9 -0
  22. data/lib/generators/polylingo_chat/templates/create_polyglot_messages.rb +13 -0
  23. data/lib/generators/polylingo_chat/templates/create_polyglot_participants.rb +12 -0
  24. data/lib/generators/polylingo_chat/templates/models/conversation.rb +7 -0
  25. data/lib/generators/polylingo_chat/templates/models/message.rb +14 -0
  26. data/lib/generators/polylingo_chat/templates/models/participant.rb +6 -0
  27. data/lib/generators/polylingo_chat/templates/polyglot.rb +53 -0
  28. data/lib/generators/polylingo_chat/templates/polylingo_chat_channel.rb +19 -0
  29. data/lib/polylingo_chat/config.rb +26 -0
  30. data/lib/polylingo_chat/engine.rb +10 -0
  31. data/lib/polylingo_chat/railtie.rb +14 -0
  32. data/lib/polylingo_chat/realtime.rb +8 -0
  33. data/lib/polylingo_chat/translate_job.rb +63 -0
  34. data/lib/polylingo_chat/translator/anthropic_client.rb +85 -0
  35. data/lib/polylingo_chat/translator/base.rb +13 -0
  36. data/lib/polylingo_chat/translator/gemini_client.rb +88 -0
  37. data/lib/polylingo_chat/translator/openai_client.rb +92 -0
  38. data/lib/polylingo_chat/translator.rb +40 -0
  39. data/lib/polylingo_chat/version.rb +3 -0
  40. data/lib/polylingo_chat.rb +11 -0
  41. metadata +138 -0
@@ -0,0 +1,85 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'digest'
4
+
5
+ module PolylingoChat
6
+ module Translator
7
+ class AnthropicClient < Base
8
+ class << self
9
+ def detect_language(text)
10
+ return 'unknown' if text.nil? || text.strip.empty?
11
+ prompt = "Detect language for the following text and return the ISO 639-1 code only:\n\n#{text}"
12
+ resp = anthropic_message(prompt, system: 'You are a language detection assistant. Return only the lowercase ISO 639-1 code.')
13
+ code = resp.to_s.strip[0,2]&.downcase
14
+ code || 'unknown'
15
+ end
16
+
17
+ def translate(text:, from: nil, to:, context: nil)
18
+ raise PolylingoChat::Error, 'target language required' if to.nil? || to.to_s.strip.empty?
19
+ return '' if text.nil?
20
+
21
+ # caching
22
+ cache_key = "polylingo_chat:#{Digest::SHA1.hexdigest([text, from, to, context].join(':'))}"
23
+ if (cache = PolylingoChat.config.cache_store)
24
+ cached = cache.get(cache_key) rescue nil
25
+ return JSON.parse(cached)['translated'] if cached
26
+ end
27
+
28
+ prompt = build_prompt(text: text, from: from, to: to, context: context)
29
+ translated = anthropic_message(prompt, system: 'You are a translation assistant. Return only the translated text with no additional commentary.')
30
+
31
+ if (cache = PolylingoChat.config.cache_store)
32
+ begin
33
+ cache.set(cache_key, { translated: translated }.to_json)
34
+ rescue => e
35
+ # ignore cache failures
36
+ end
37
+ end
38
+
39
+ translated
40
+ end
41
+
42
+ private
43
+
44
+ def build_prompt(text:, from:, to:, context:)
45
+ ctx = context ? "CONTEXT:\n#{context}\n\n" : ''
46
+ src = from ? "(source language: #{from})" : '(source language unknown)'
47
+ "Translate the following text to #{to}. #{src}\n\n#{ctx}TEXT:\n#{text}"
48
+ end
49
+
50
+ def anthropic_message(prompt, system: nil)
51
+ api_key = PolylingoChat.config.api_key
52
+ raise PolylingoChat::Error, 'API key not configured' unless api_key
53
+
54
+ conn = Faraday.new(url: 'https://api.anthropic.com', request: { timeout: PolylingoChat.config.timeout, open_timeout: 5 }) do |f|
55
+ f.request :json
56
+ f.adapter Faraday.default_adapter
57
+ end
58
+
59
+ body = {
60
+ model: PolylingoChat.config.model || 'claude-3-5-sonnet-20241022',
61
+ max_tokens: 1024,
62
+ messages: [
63
+ { role: 'user', content: prompt }
64
+ ]
65
+ }
66
+ body[:system] = system if system
67
+
68
+ res = conn.post('/v1/messages') do |r|
69
+ r.headers['x-api-key'] = api_key
70
+ r.headers['anthropic-version'] = '2023-06-01'
71
+ r.headers['Content-Type'] = 'application/json'
72
+ r.body = body.to_json
73
+ end
74
+
75
+ if res.status >= 400
76
+ raise PolylingoChat::Error, "Anthropic API error: #{res.status} - #{res.body}"
77
+ end
78
+
79
+ parsed = JSON.parse(res.body)
80
+ parsed.dig('content', 0, 'text') || ''
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,13 @@
1
+ module PolylingoChat
2
+ module Translator
3
+ class Base
4
+ def self.detect_language(text)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def self.translate(text:, from:, to:, context:)
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,88 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'digest'
4
+
5
+ module PolylingoChat
6
+ module Translator
7
+ class GeminiClient < Base
8
+ class << self
9
+ def detect_language(text)
10
+ return 'unknown' if text.nil? || text.strip.empty?
11
+ prompt = "Detect language for the following text and return the ISO 639-1 code only:\n\n#{text}"
12
+ resp = gemini_generate(prompt, system: 'You are a language detection assistant. Return only the lowercase ISO 639-1 code.')
13
+ code = resp.to_s.strip[0,2]&.downcase
14
+ code || 'unknown'
15
+ end
16
+
17
+ def translate(text:, from: nil, to:, context: nil)
18
+ raise PolylingoChat::Error, 'target language required' if to.nil? || to.to_s.strip.empty?
19
+ return '' if text.nil?
20
+
21
+ # caching
22
+ cache_key = "polylingo_chat:#{Digest::SHA1.hexdigest([text, from, to, context].join(':'))}"
23
+ if (cache = PolylingoChat.config.cache_store)
24
+ cached = cache.get(cache_key) rescue nil
25
+ return JSON.parse(cached)['translated'] if cached
26
+ end
27
+
28
+ prompt = build_prompt(text: text, from: from, to: to, context: context)
29
+ translated = gemini_generate(prompt, system: 'You are a translation assistant. Return only the translated text with no additional commentary.')
30
+
31
+ if (cache = PolylingoChat.config.cache_store)
32
+ begin
33
+ cache.set(cache_key, { translated: translated }.to_json)
34
+ rescue => e
35
+ # ignore cache failures
36
+ end
37
+ end
38
+
39
+ translated
40
+ end
41
+
42
+ private
43
+
44
+ def build_prompt(text:, from:, to:, context:)
45
+ ctx = context ? "CONTEXT:\n#{context}\n\n" : ''
46
+ src = from ? "(source language: #{from})" : '(source language unknown)'
47
+ "Translate the following text to #{to}. #{src}\n\n#{ctx}TEXT:\n#{text}"
48
+ end
49
+
50
+ def gemini_generate(prompt, system: nil)
51
+ api_key = PolylingoChat.config.api_key
52
+ raise PolylingoChat::Error, 'API key not configured' unless api_key
53
+
54
+ model = PolylingoChat.config.model || 'gemini-1.5-flash'
55
+
56
+ conn = Faraday.new(url: 'https://generativelanguage.googleapis.com', request: { timeout: PolylingoChat.config.timeout, open_timeout: 5 }) do |f|
57
+ f.request :json
58
+ f.adapter Faraday.default_adapter
59
+ end
60
+
61
+ contents = []
62
+ contents << { role: 'user', parts: [{ text: system }] } if system
63
+ contents << { role: 'user', parts: [{ text: prompt }] }
64
+
65
+ body = {
66
+ contents: contents,
67
+ generationConfig: {
68
+ temperature: 0.0,
69
+ maxOutputTokens: 1024
70
+ }
71
+ }
72
+
73
+ res = conn.post("/v1beta/models/#{model}:generateContent?key=#{api_key}") do |r|
74
+ r.headers['Content-Type'] = 'application/json'
75
+ r.body = body.to_json
76
+ end
77
+
78
+ if res.status >= 400
79
+ raise PolylingoChat::Error, "Gemini API error: #{res.status} - #{res.body}"
80
+ end
81
+
82
+ parsed = JSON.parse(res.body)
83
+ parsed.dig('candidates', 0, 'content', 'parts', 0, 'text') || ''
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,92 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'digest'
4
+
5
+ module PolylingoChat
6
+ module Translator
7
+ class OpenAIClient < Base
8
+ class << self
9
+ def detect_language(text)
10
+ return 'unknown' if text.nil? || text.strip.empty?
11
+ prompt = "Detect language for the following text and return the ISO 639-1 code only:
12
+
13
+ #{text}"
14
+ resp = openai_chat(prompt, system: 'You are a language detection assistant. Return only the lowercase ISO 639-1 code.')
15
+ code = resp.to_s.strip[0,2]&.downcase
16
+ code || 'unknown'
17
+ end
18
+
19
+ def translate(text:, from: nil, to:, context: nil)
20
+ raise PolylingoChat::Error, 'target language required' if to.nil? || to.to_s.strip.empty?
21
+ return '' if text.nil?
22
+
23
+ # caching
24
+ cache_key = "polylingo_chat:#{Digest::SHA1.hexdigest([text, from, to, context].join(':'))}"
25
+ if (cache = PolylingoChat.config.cache_store)
26
+ cached = cache.get(cache_key) rescue nil
27
+ return JSON.parse(cached)['translated'] if cached
28
+ end
29
+
30
+ prompt = build_prompt(text: text, from: from, to: to, context: context)
31
+ translated = openai_chat(prompt, system: 'You are a translation assistant. Return only the translated text with no additional commentary.')
32
+
33
+ if (cache = PolylingoChat.config.cache_store)
34
+ begin
35
+ cache.set(cache_key, { translated: translated }.to_json)
36
+ rescue => e
37
+ # ignore cache failures
38
+ end
39
+ end
40
+
41
+ translated
42
+ end
43
+
44
+ private
45
+
46
+ def build_prompt(text:, from:, to:, context:)
47
+ ctx = context ? "CONTEXT:
48
+ #{context}
49
+
50
+ " : ''
51
+ src = from ? "(source language: #{from})" : '(source language unknown)'
52
+ "Translate the following text to #{to}. #{src}
53
+
54
+ #{ctx}TEXT:
55
+ #{text}"
56
+ end
57
+
58
+ def openai_chat(prompt, system: nil)
59
+ api_key = PolylingoChat.config.api_key
60
+ raise PolylingoChat::Error, 'API key not configured' unless api_key
61
+
62
+ conn = Faraday.new(url: 'https://api.openai.com', request: { timeout: PolylingoChat.config.timeout, open_timeout: 5 }) do |f|
63
+ f.request :json
64
+ f.adapter Faraday.default_adapter
65
+ end
66
+
67
+ body = {
68
+ model: PolylingoChat.config.model,
69
+ messages: [
70
+ { role: 'system', content: system || 'You are a helpful assistant.' },
71
+ { role: 'user', content: prompt }
72
+ ],
73
+ temperature: 0.0
74
+ }
75
+
76
+ res = conn.post('/v1/chat/completions') do |r|
77
+ r.headers['Authorization'] = "Bearer #{api_key}"
78
+ r.headers['Content-Type'] = 'application/json'
79
+ r.body = body.to_json
80
+ end
81
+
82
+ if res.status >= 400
83
+ raise PolylingoChat::Error, "OpenAI API error: #{res.status} - #{res.body}"
84
+ end
85
+
86
+ parsed = JSON.parse(res.body)
87
+ parsed.dig('choices', 0, 'message', 'content') || ''
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'translator/base'
2
+ require_relative 'translator/openai_client'
3
+ require_relative 'translator/anthropic_client'
4
+ require_relative 'translator/gemini_client'
5
+
6
+ module PolylingoChat
7
+ module Translator
8
+ class << self
9
+ def detect_language(text)
10
+ provider_client.detect_language(text)
11
+ end
12
+
13
+ def translate(text:, from: nil, to:, context: nil)
14
+ provider_client.translate(text: text, from: from, to: to, context: context)
15
+ end
16
+
17
+ def provider_client
18
+ @provider_client ||= configure_provider
19
+ end
20
+
21
+ def configure_provider
22
+ case PolylingoChat.config.provider
23
+ when :openai
24
+ PolylingoChat::Translator::OpenAIClient
25
+ when :anthropic
26
+ PolylingoChat::Translator::AnthropicClient
27
+ when :gemini
28
+ PolylingoChat::Translator::GeminiClient
29
+ else
30
+ PolylingoChat::Translator::OpenAIClient
31
+ end
32
+ end
33
+
34
+ def reset_provider!
35
+ @provider_client = nil
36
+ configure_provider
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module PolylingoChat
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'polylingo_chat/version'
2
+ require 'polylingo_chat/engine'
3
+ require 'polylingo_chat/config'
4
+ require 'polylingo_chat/railtie'
5
+ require 'polylingo_chat/translator'
6
+ require 'polylingo_chat/translate_job'
7
+ require 'polylingo_chat/realtime'
8
+
9
+ module PolylingoChat
10
+ class Error < StandardError; end
11
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: polylingo_chat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shoaib Malik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - shoaib2109@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - lib/generators/polylingo_chat/install/install_generator.rb
78
+ - lib/generators/polylingo_chat/install/templates/README
79
+ - lib/generators/polylingo_chat/install/templates/channels/application_cable/channel.rb
80
+ - lib/generators/polylingo_chat/install/templates/channels/application_cable/connection.rb
81
+ - lib/generators/polylingo_chat/install/templates/channels/polylingo_chat_channel.rb
82
+ - lib/generators/polylingo_chat/install/templates/create_conversations.rb
83
+ - lib/generators/polylingo_chat/install/templates/create_messages.rb
84
+ - lib/generators/polylingo_chat/install/templates/create_participants.rb
85
+ - lib/generators/polylingo_chat/install/templates/initializer.rb
86
+ - lib/generators/polylingo_chat/install/templates/javascript/channels/consumer.js
87
+ - lib/generators/polylingo_chat/install/templates/javascript/channels/index.js
88
+ - lib/generators/polylingo_chat/install/templates/javascript/chat.js
89
+ - lib/generators/polylingo_chat/install/templates/models/conversation.rb
90
+ - lib/generators/polylingo_chat/install/templates/models/message.rb
91
+ - lib/generators/polylingo_chat/install/templates/models/participant.rb
92
+ - lib/generators/polylingo_chat/install_generator.rb
93
+ - lib/generators/polylingo_chat/templates/INSTALL_README.md
94
+ - lib/generators/polylingo_chat/templates/chat_channel_example.js
95
+ - lib/generators/polylingo_chat/templates/create_polyglot_conversations.rb
96
+ - lib/generators/polylingo_chat/templates/create_polyglot_messages.rb
97
+ - lib/generators/polylingo_chat/templates/create_polyglot_participants.rb
98
+ - lib/generators/polylingo_chat/templates/models/conversation.rb
99
+ - lib/generators/polylingo_chat/templates/models/message.rb
100
+ - lib/generators/polylingo_chat/templates/models/participant.rb
101
+ - lib/generators/polylingo_chat/templates/polyglot.rb
102
+ - lib/generators/polylingo_chat/templates/polylingo_chat_channel.rb
103
+ - lib/polylingo_chat.rb
104
+ - lib/polylingo_chat/config.rb
105
+ - lib/polylingo_chat/engine.rb
106
+ - lib/polylingo_chat/railtie.rb
107
+ - lib/polylingo_chat/realtime.rb
108
+ - lib/polylingo_chat/translate_job.rb
109
+ - lib/polylingo_chat/translator.rb
110
+ - lib/polylingo_chat/translator/anthropic_client.rb
111
+ - lib/polylingo_chat/translator/base.rb
112
+ - lib/polylingo_chat/translator/gemini_client.rb
113
+ - lib/polylingo_chat/translator/openai_client.rb
114
+ - lib/polylingo_chat/version.rb
115
+ homepage: https://github.com/AdwareTechnologies/polylingo_chat
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 2.7.0
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.4.10
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Realtime chat with automatic AI translation for Ruby/Rails apps
138
+ test_files: []