PolylingoChat — Real-time Multilingual Chat Engine for Rails
PolylingoChat is a plug-and-play Rails Engine that adds real-time chat to your Rails app. Use it as a chat-only engine or enable AI-powered translation for multilingual conversations.
Perfect for marketplaces, SaaS apps, CRMs, support systems, or global communities.
✨ Features
- 🔥 Real-time chat using ActionCable with Solid Cable (database-backed)
- 🚀 One-command installer — everything set up automatically
- 🌍 Optional AI translation - works with or without translation
- ⚙️ Multi-provider support — OpenAI, Anthropic Claude, or Google Gemini
- 🧩 Rails Engine — mounts directly inside your app
- 👥 Conversations, Participants & Messages models included
- 🔌 Works with any ActiveJob backend (Sidekiq, Solid Queue, Delayed Job, etc.)
- 🔐 Secure & scoped ActionCable channels
- 🧱 Extendable architecture (custom UI, custom providers, custom storage)
- 💾 Built-in caching support for translations
- 🧪 RSpec testing framework included
- 🗄️ No Redis required — uses Solid Cable for WebSocket persistence
📋 Requirements
- Ruby >= 2.7.0
- Rails >= 6.0
- A background job processor (Sidekiq, Solid Queue, or Delayed Job)
- Optional: AI API key (only if you want translation)
Note: PolylingoChat uses Solid Cable (database-backed ActionCable) by default, so Redis is optional.
🚀 Installation
1. Add the gem
# Gemfile
gem "polylingo_chat", github: "AdwareTechnologies/polylingo_chat"
# Add a background job processor
gem 'sidekiq' # or 'solid_queue' or 'delayed_job_active_record'
2. Install and generate
bundle install
bin/rails generate polylingo_chat:install
bin/rails db:migrate
The installer automatically:
- ✅ Creates migrations for Conversations, Participants, and Messages
- ✅ Generates model files with associations
- ✅ Sets up ActionCable channels (PolylinguoChatChannel)
- ✅ Creates JavaScript files for real-time chat
- ✅ Configures Solid Cable in
config/cable.yml - ✅ Downloads ActionCable ESM module to
vendor/javascript - ✅ Updates
config/routes.rbwith chat routes - ✅ Updates
config/importmap.rbwith required pins - ✅ Updates
app/javascript/application.jswith chat import - ✅ Creates
config/initializers/polylingo_chat.rbfor configuration
3. Configure ActiveJob
# config/application.rb or config/environments/production.rb
config.active_job.queue_adapter = :sidekiq # or :solid_queue, :delayed_job, :async
4. Add preferred_language to User (optional, only if using translation)
bin/rails generate migration AddPreferredLanguageToUsers preferred_language:string
bin/rails db:migrate
5. Configure PolylingoChat
Edit config/initializers/polylingo_chat.rb (created by installer):
Option A: Chat-Only Mode (No Translation)
PolylingoChat.configure do |config|
# Leave api_key as nil for chat-only mode
config.api_key = nil
config.queue_adapter = :sidekiq
config.async = true
end
Option B: With AI Translation
PolylingoChat.configure do |config|
# Enable translation by setting API key
config.provider = :openai # or :anthropic, :gemini
config.api_key = ENV['OPENAI_API_KEY']
config.model = 'gpt-4o-mini'
config.queue_adapter = :sidekiq
config.default_language = 'en'
config.cache_store = Rails.cache
config.timeout = 15
config.async = true
end
6. Configure ActionCable Authentication
The installer creates app/channels/application_cable/connection.rb with authentication logic. For development, it accepts user_id from WebSocket params:
# app/channels/application_cable/connection.rb
def find_verified_user
user_id = request.params[:user_id] || .encrypted[:user_id]
if user_id && (verified_user = User.find_by(id: user_id))
verified_user
else
end
end
The JavaScript consumer automatically passes window.currentUserId to authenticate. Make sure to set this in your views:
<!-- app/views/conversations/show.html.erb -->
<script>
window.conversationId = <%= @conversation.id %>;
window.currentUserId = <%= current_user.id %>;
</script>
For production: Replace the find_verified_user method with your actual authentication (Devise, session, JWT, etc.)
7. Start your background worker
bundle exec sidekiq # or bin/rails solid_queue:start, or bin/rails jobs:work
🎯 Usage
1. Set up your view
First, expose the conversation and user IDs to JavaScript in your chat view:
<!-- app/views/conversations/show.html.erb -->
<script>
window.conversationId = <%= @conversation.id %>;
window.currentUserId = <%= current_user.id %>;
</script>
<div id="messages">
<!-- Messages will appear here via ActionCable -->
</div>
2. Send a message
# Create a conversation
conversation = Conversation.create!(title: "Project Discussion")
# Add participants
Participant.create!(conversation: conversation, user: user1)
Participant.create!(conversation: conversation, user: user2)
# Send a message
Message.create!(
conversation: conversation,
sender: user1,
body: "Hello! How are you?"
)
What happens:
- Message is saved to database
- Background job is enqueued automatically
- Without API key: Message is broadcast as-is to all participants
- With API key: Message is translated to each participant's preferred language
- All participants see the message in real-time via ActionCable
3. Real-time updates with ActionCable
The installer creates JavaScript files that handle real-time updates automatically. The key file is app/javascript/chat.js:
// This file is automatically generated by the installer
// It connects to PolylinguoChatChannel and handles incoming messages
import consumer from "channels/consumer"
consumer.subscriptions.create({
channel: "PolylinguoChatChannel",
conversation_id: window.conversationId
}, {
received(data) {
// data.message - message text (translated if translation enabled)
// data.original - original text
// data.sender_id - sender's ID
// data.translated - boolean (true if translation was used)
console.log("Received:", data.message)
console.log("Was translated:", data.translated)
// The generated code automatically appends messages to #messages div
}
})
You don't need to write this code — the installer creates it for you!
🌍 Translation (Optional)
When to Use Translation
Translation is optional. Use it when:
- ✅ You have a global user base speaking different languages
- ✅ You want automatic message translation
- ✅ You're willing to pay for AI API usage
Skip translation when:
- ❌ All users speak the same language
- ❌ You want a simple chat without AI costs
- ❌ You'll handle translation elsewhere
AI Providers
OpenAI
config.provider = :openai
config.api_key = ENV['OPENAI_API_KEY']
config.model = 'gpt-4o-mini' # Fast & cost-effective
Get key: https://platform.openai.com/api-keys
Models: gpt-4o-mini, gpt-4o, gpt-3.5-turbo
Anthropic Claude
config.provider = :anthropic
config.api_key = ENV['ANTHROPIC_API_KEY']
config.model = 'claude-3-5-sonnet-20241022'
Get key: https://console.anthropic.com/
Models: claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022, claude-3-opus-20240229
Google Gemini
config.provider = :gemini
config.api_key = ENV['GOOGLE_API_KEY']
config.model = 'gemini-1.5-flash'
Get key: https://ai.google.dev/
Models: gemini-1.5-flash, gemini-1.5-pro
⚙️ Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
provider |
Symbol | :openai |
AI provider (:openai, :anthropic, :gemini) |
api_key |
String | nil |
API key - leave nil for chat-only mode |
model |
String | 'gpt-4o-mini' |
Model name for the provider |
queue_adapter |
Symbol | nil |
Which ActiveJob adapter you're using (informational) |
default_language |
String | 'en' |
Default target language (ISO 639-1 code) |
cache_store |
Cache | nil |
Rails cache store for caching translations |
async |
Boolean | true |
Enable async processing via ActiveJob |
timeout |
Integer | 15 |
API request timeout in seconds |
🏗️ How It Works
Message Flow
- User sends a message → saved to database
Message#after_create_commitenqueues job- Job runs in background via your ActiveJob adapter
- If API key present: Message translated for each participant
- If no API key: Original message used for all participants
- Messages broadcast via ActionCable (using Solid Cable)
- Recipients see messages in real-time
Models
Conversation - Chat conversation with many participants and messages
Participant - Join table linking Users to Conversations
Message - Individual chat message with translation tracking
Solid Cable (Database-Backed WebSockets)
PolylingoChat uses Solid Cable instead of Redis for ActionCable. Benefits:
- ✅ No Redis dependency - One less service to manage
- ✅ Message persistence - WebSocket messages stored in database
- ✅ Simpler deployment - Works anywhere your database works
- ✅ Better for development - No need to run Redis locally
- ✅ Cost-effective - No separate Redis hosting required
Solid Cable uses your existing database (SQLite, PostgreSQL, MySQL) to store WebSocket messages with configurable retention:
# config/cable.yml (configured automatically by installer)
development:
adapter: solid_cable
polling_interval: 0.1.seconds
message_retention: 1.day
The installer runs bin/rails solid_cable:install automatically, which creates the necessary migrations.
🧪 Running Tests
bundle exec rspec
# Current status:
# 29 examples, 0 failures
# Line Coverage: 91.02%
🚀 Performance Tips
1. Enable Caching (if using translation)
config.cache_store = Rails.cache
2. Choose the Right Model
- High-volume:
gpt-4o-mini,claude-3-5-haiku,gemini-1.5-flash - Quality:
gpt-4o,claude-3-5-sonnet,gemini-1.5-pro
3. Scale Your Job Processor
Sidekiq:
# config/sidekiq.yml
:concurrency: 25
:queues:
- [polylingo_chat_translations, 10]
- [default, 5]
Solid Queue:
# config/solid_queue.yml
workers:
- queues: polylingo_chat_translations
threads: 5
processes: 3
🔒 Security
- API Keys: Use environment variables, never commit
- User Scoping: Ensure users can only access their conversations
- ActionCable Auth: Secure channels with authentication
- Rate Limiting: Consider limiting message creation
- Content Filtering: Add profanity/spam filters if needed
💡 Use Cases
Chat-Only Mode (No Translation)
Perfect for:
- Internal team chat
- Customer support (same language)
- Community forums
- Simple messaging features
With Translation
Perfect for:
- Global marketplaces
- International teams
- Multi-language support systems
- Cross-border collaboration
🤝 Contributing
- Fork the repository
- Create your feature branch
- Write tests for your changes
- Commit and push
- Open a Pull Request
📝 License
MIT License
👤 Author
Shoaib Malik
- Email: [email protected]
- GitHub: @shoaibmalik786
📞 Support
- 🐛 Issues: GitHub Issues
- 📖 Documentation: GitHub Wiki
- 💬 Discussions: GitHub Discussions
Made with ❤️ for the global Rails community