internationalize 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ba5df71bafb02b68108aed602ac75ad4ced036109e49dbd40dc90354a7c351a
4
- data.tar.gz: d39b4232e8b76511d1a409d6848fa98e8f60f8f9523b62985c467cb2767ec7e3
3
+ metadata.gz: 520c17d4993a0145c00081a95a30f21c9a3b56b0879589fc87f4731564be7f1e
4
+ data.tar.gz: 24478d271a6500c4104c8e26db4238ed1857b533b25e5c351e30cfc45fe7c13a
5
5
  SHA512:
6
- metadata.gz: 8ff4adb5021405380a123cc1cc06b8dd3818851e32570ae90e7bdf776f566bbc9ff6504ab26960dae909109550444875aaca42d3cf988ae4393b18c05628e292
7
- data.tar.gz: 8d2643b882281a39f0446d274b25321be03909c2ba41510a75a4e498918fc0ee37a59abae42e373e246a4d1b1f1ebeefdb5ec9e12e29d8a81a13c3d05f9478a6
6
+ metadata.gz: a15ca8db8848c85feec7d912c47905a0cbd665aed43721642b97e039f607699edbeb207666d3af98e92e412d6c6ac0c89298b4ac4aba5c9643bf2554c2f59a08
7
+ data.tar.gz: b380cf31e89bd26994935e733c89df8b23771e83a73606f39dd6092f833aec11f0a4e471ec8c8c8ff914cd3079a15b0d05a4d016962936adc707302acaec5683
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2024-11-29
11
+
12
+ ### Added
13
+
14
+ - ActionText support via `international_rich_text` (optional, requires ActionText)
15
+ - Generates `has_rich_text` for each locale with unified accessor
16
+ - Full attachment support per locale
17
+
18
+ ### Removed
19
+
20
+ - `fallback: false` option - translations now always fallback to default locale
21
+ - `set_translation(attr, locale, value)` - use `title_de = "value"` instead
22
+ - `translation_for(attr, locale)` - use `title_de` instead
23
+
24
+ ## [0.1.1] - 2024-11-29
25
+
26
+ ### Fixed
27
+
28
+ - Include `context/` directory in gem for `bake agent:context:install` support
29
+
8
30
  ## [0.1.0] - 2024-11-29
9
31
 
10
32
  ### Added
@@ -28,7 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
50
  - `translation_for(attr, locale)`
29
51
  - `translated?(attr, locale)`
30
52
  - `translated_locales(attr)`
31
- - Fallback to default locale when translation missing (configurable)
53
+ - Fallback to default locale when translation missing
32
54
  - SQLite adapter using `json_extract()`
33
55
  - PostgreSQL adapter using `->>` operator
34
56
  - MySQL 8+ adapter using `->>` operator (supports mysql2 and trilogy gems)
data/README.md CHANGED
@@ -4,10 +4,14 @@ Lightweight, performant internationalization for Rails with JSON column storage.
4
4
 
5
5
  ## Why Internationalize?
6
6
 
7
+ Internationalize is a focused, lightweight gem that does one thing well: JSON column translations. No backend abstraction layers, no plugin systems, no extra memory overhead.
8
+
7
9
  Unlike Globalize or Mobility which use separate translation tables requiring JOINs, Internationalize stores translations inline using JSON columns. This means:
8
10
 
9
11
  - **No JOINs** - translations live in the same table
10
12
  - **No N+1 queries** - data is always loaded with the record
13
+ - **No backend overhead** - direct JSON column access, no abstraction layers
14
+ - **~50% less memory** - no per-instance backend objects or plugin chains
11
15
  - **Direct method dispatch** - no `method_missing` overhead
12
16
  - **Multi-database support** - works with SQLite, PostgreSQL, and MySQL
13
17
  - **Visible in schema.rb** - translated fields appear directly in your model's schema
@@ -155,10 +159,6 @@ article.translated?(:title, :de) # => true/false
155
159
 
156
160
  # Get all translated locales for an attribute
157
161
  article.translated_locales(:title) # => [:en, :de]
158
-
159
- # Set/get translation for specific locale
160
- article.set_translation(:title, :de, "Hallo Welt")
161
- article.translation_for(:title, :de) # => "Hallo Welt"
162
162
  ```
163
163
 
164
164
  ### Fallbacks
@@ -173,10 +173,29 @@ I18n.locale = :de
173
173
  article.title # => "Hello" (falls back to :en)
174
174
  ```
175
175
 
176
- Disable fallbacks:
176
+ ### ActionText Support
177
+
178
+ For rich text with attachments, use `international_rich_text` (requires ActionText):
177
179
 
178
180
  ```ruby
179
- international :title, fallback: false
181
+ require "internationalize/rich_text"
182
+
183
+ class Article < ApplicationRecord
184
+ include Internationalize::Model
185
+ include Internationalize::RichText
186
+
187
+ international_rich_text :content
188
+ end
189
+ ```
190
+
191
+ This generates `has_rich_text :content_en`, `has_rich_text :content_de`, etc. for each locale, with a unified accessor:
192
+
193
+ ```ruby
194
+ article.content = "<p>Hello</p>" # Sets for current locale
195
+ article.content # Gets for current locale (with fallback)
196
+ article.content_en # Direct access to English
197
+ article.content.body # ActionText::Content object
198
+ article.content.embeds # Attachments work per-locale
180
199
  ```
181
200
 
182
201
  ## Configuration
@@ -184,11 +203,12 @@ international :title, fallback: false
184
203
  ```ruby
185
204
  # config/initializers/internationalize.rb
186
205
  Internationalize.configure do |config|
187
- config.fallback_locale = :en
188
- config.available_locales = [:en, :de, :fr]
206
+ config.available_locales = [:en, :de, :fr] # Defaults to I18n.available_locales
189
207
  end
190
208
  ```
191
209
 
210
+ Fallback uses `I18n.default_locale` automatically.
211
+
192
212
  ## Performance Comparison
193
213
 
194
214
  Benchmark with 1000 records, 2 translated attributes (title + body), 3 locales:
@@ -0,0 +1,66 @@
1
+ # Internationalize: Configuration
2
+
3
+ ## Global Configuration
4
+
5
+ ```ruby
6
+ # config/initializers/internationalize.rb
7
+ Internationalize.configure do |config|
8
+ # Override available locales (defaults to I18n.available_locales)
9
+ config.available_locales = [:en, :de, :fr, :es]
10
+ end
11
+ ```
12
+
13
+ Fallback locale is always `I18n.default_locale`.
14
+
15
+ ## Database Setup
16
+
17
+ Use the generator to create migrations:
18
+
19
+ ```bash
20
+ rails generate internationalize:translation Article title description
21
+ ```
22
+
23
+ Or create migrations manually:
24
+
25
+ ### SQLite / MySQL
26
+
27
+ ```ruby
28
+ class AddTranslationsToArticles < ActiveRecord::Migration[7.1]
29
+ def change
30
+ add_column :articles, :title_translations, :json, default: {}
31
+ end
32
+ end
33
+ ```
34
+
35
+ ### PostgreSQL
36
+
37
+ For better query performance, use `jsonb`:
38
+
39
+ ```ruby
40
+ class AddTranslationsToArticles < ActiveRecord::Migration[7.1]
41
+ def change
42
+ add_column :articles, :title_translations, :jsonb, default: {}
43
+
44
+ # Optional: Add GIN index for faster queries
45
+ add_index :articles, :title_translations, using: :gin
46
+ end
47
+ end
48
+ ```
49
+
50
+ ## Supported Databases
51
+
52
+ | Database | Minimum Version | JSON Type | Query Syntax |
53
+ |----------|-----------------|-----------|--------------|
54
+ | SQLite | 3.38+ | `json` | `json_extract()` |
55
+ | PostgreSQL | 9.4+ | `json`/`jsonb` | `->>` operator |
56
+ | MySQL | 8.0+ | `json` | `->>` operator |
57
+
58
+ ## I18n Integration
59
+
60
+ Internationalize uses Rails I18n settings directly:
61
+
62
+ ```ruby
63
+ I18n.locale # Used for current locale
64
+ I18n.default_locale # Used for fallbacks
65
+ I18n.available_locales # Used for locale-specific accessors (can be overridden)
66
+ ```
@@ -0,0 +1,98 @@
1
+ # Internationalize: Getting Started
2
+
3
+ Internationalize is a lightweight internationalization gem for Rails that stores translations in JSON columns instead of separate tables.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ # Gemfile
9
+ gem "internationalize"
10
+ ```
11
+
12
+ ## Quick Setup
13
+
14
+ ### 1. Generate a migration for translation columns
15
+
16
+ ```bash
17
+ rails generate internationalize:translation Article title description
18
+ ```
19
+
20
+ This creates a migration adding `title_translations` and `description_translations` JSON columns.
21
+
22
+ Or write the migration manually:
23
+
24
+ ```ruby
25
+ class AddTranslationsToArticles < ActiveRecord::Migration[7.1]
26
+ def change
27
+ add_column :articles, :title_translations, :json, default: {}
28
+ add_column :articles, :description_translations, :json, default: {}
29
+ end
30
+ end
31
+ ```
32
+
33
+ ### 2. Include the mixin and declare translatable attributes
34
+
35
+ ```ruby
36
+ class Article < ApplicationRecord
37
+ include Internationalize::Model
38
+ international :title, :description
39
+ end
40
+ ```
41
+
42
+ ### 3. Use translations
43
+
44
+ ```ruby
45
+ # Create with translations (hash for multiple locales)
46
+ Article.international_create!(
47
+ title: { en: "Hello World", de: "Hallo Welt" },
48
+ status: "published"
49
+ )
50
+
51
+ # Or direct string for current locale
52
+ I18n.locale = :de
53
+ Article.international_create!(title: "Achtung!") # Sets title_de
54
+
55
+ # Set translation for current locale
56
+ article.title = "Hello World"
57
+
58
+ # Set for specific locale
59
+ article.title_en = "Hello World"
60
+ article.title_de = "Hallo Welt"
61
+
62
+ # Read translation
63
+ article.title # => uses I18n.locale
64
+ article.title_de # => "Hallo Welt"
65
+
66
+ # Access raw hash
67
+ article.title_translations # => {"en" => "Hello World", "de" => "Hallo Welt"}
68
+ ```
69
+
70
+ ### 4. Query translations
71
+
72
+ ```ruby
73
+ # Exact match
74
+ Article.international(title: "Hello World")
75
+
76
+ # Partial match (LIKE)
77
+ Article.international(title: "Hello", match: :partial)
78
+
79
+ # Order by translation
80
+ Article.international_order(:title, :desc)
81
+ ```
82
+
83
+ ## Key Concepts
84
+
85
+ - Translations stored in `*_translations` JSON columns
86
+ - No JOINs - data lives in the same table
87
+ - Automatic fallback to default locale
88
+ - Works with SQLite, PostgreSQL, and MySQL
89
+
90
+ ## Important: Column Defaults
91
+
92
+ JSON columns **must** have `default: {}` set in the migration. The generator does this automatically, but if writing migrations manually, ensure you include it:
93
+
94
+ ```ruby
95
+ add_column :articles, :title_translations, :json, default: {}
96
+ ```
97
+
98
+ This is required for optimal performance and correct behavior.
@@ -0,0 +1,14 @@
1
+ description: "Lightweight, performant internationalization for Rails using JSON columns"
2
+ files:
3
+ - path: getting-started.md
4
+ title: "Getting Started"
5
+ description: "Quick setup guide for Internationalize"
6
+ - path: model-api.md
7
+ title: "Model API"
8
+ description: "Model mixin methods and generated accessors"
9
+ - path: query-api.md
10
+ title: "Query API"
11
+ description: "Query methods for searching and filtering translations"
12
+ - path: configuration.md
13
+ title: "Configuration"
14
+ description: "Global and per-attribute configuration options"
@@ -0,0 +1,81 @@
1
+ # Internationalize: Model API
2
+
3
+ ## Including the Mixin
4
+
5
+ ```ruby
6
+ class Article < ApplicationRecord
7
+ include Internationalize::Model
8
+ international :title, :description
9
+ end
10
+ ```
11
+
12
+ ## Generated Instance Methods
13
+
14
+ For each `international :title` declaration:
15
+
16
+ ### Accessors
17
+
18
+ | Method | Description |
19
+ |--------|-------------|
20
+ | `title` | Get translation for current `I18n.locale` |
21
+ | `title=` | Set translation for current `I18n.locale` |
22
+ | `title?` | Check if translation exists |
23
+ | `title_translations` | Get raw hash of all translations |
24
+ | `title_translations=` | Set all translations at once |
25
+
26
+ ### Locale-Specific Accessors
27
+
28
+ For each locale in `I18n.available_locales`:
29
+
30
+ | Method | Description |
31
+ |--------|-------------|
32
+ | `title_en` | Get English translation directly |
33
+ | `title_en=` | Set English translation |
34
+ | `title_en?` | Check if English translation exists |
35
+ | `title_de` | Get German translation |
36
+ | `title_de=` | Set German translation |
37
+ | ... | etc. for all locales |
38
+
39
+ ### Instance Helper Methods
40
+
41
+ | Method | Description |
42
+ |--------|-------------|
43
+ | `translated?(:title, :de)` | Check if translation exists |
44
+ | `translated_locales(:title)` | Array of locales with translations |
45
+
46
+ ## Class Methods (Querying)
47
+
48
+ All query methods default to current `I18n.locale` and return `ActiveRecord::Relation`:
49
+
50
+ | Method | Description |
51
+ |--------|-------------|
52
+ | `international(**conditions, locale: nil)` | Exact match query |
53
+ | `international_not(**conditions, locale: nil)` | Exclude matching records |
54
+ | `international_search(**conditions, locale: nil, case_sensitive: false)` | Substring search |
55
+ | `international_order(attr, dir = :asc, locale: nil)` | Order by translation |
56
+ | `translated(*attrs, locale: nil)` | Find records with translation |
57
+ | `untranslated(*attrs, locale: nil)` | Find records missing translation |
58
+
59
+ ```ruby
60
+ # Examples
61
+ Article.international(title: "Hello World")
62
+ Article.international_search(title: "hello", locale: :de)
63
+ Article.international_order(:title, :desc)
64
+ Article.translated(:title, locale: :de)
65
+ ```
66
+
67
+ ## Class Attributes
68
+
69
+ ```ruby
70
+ Article.international_attributes # => [:title, :description]
71
+ ```
72
+
73
+ ## Fallback Behavior
74
+
75
+ Translations automatically fall back to the default locale when missing:
76
+
77
+ ```ruby
78
+ article.title_en = "Hello"
79
+ I18n.locale = :de
80
+ article.title # => "Hello" (falls back to default locale)
81
+ ```
@@ -0,0 +1,129 @@
1
+ # Internationalize: Query API
2
+
3
+ All query methods are class methods on models that include `Internationalize::Model`. They return `ActiveRecord::Relation` objects that can be chained with standard AR methods.
4
+
5
+ ## Default Locale Behavior
6
+
7
+ All query methods default to the current `I18n.locale`. Use the `locale:` option to override:
8
+
9
+ ```ruby
10
+ # Uses current I18n.locale
11
+ Article.international(title: "Hello World")
12
+
13
+ # Explicit locale
14
+ Article.international(title: "Hallo Welt", locale: :de)
15
+ ```
16
+
17
+ ## Query Methods
18
+
19
+ ### international(**conditions, locale: nil)
20
+
21
+ Exact match on translated attributes:
22
+
23
+ ```ruby
24
+ Article.international(title: "Hello World")
25
+ Article.international(title: "Hello", status: "published")
26
+ Article.international(title: "Hallo Welt", locale: :de)
27
+ ```
28
+
29
+ ### international_not(**conditions, locale: nil)
30
+
31
+ Exclude records matching conditions:
32
+
33
+ ```ruby
34
+ Article.international_not(title: "Draft")
35
+ Article.international_not(title: "Entwurf", locale: :de)
36
+ ```
37
+
38
+ ### international_search(**conditions, locale: nil, case_sensitive: false)
39
+
40
+ Substring search (LIKE/ILIKE):
41
+
42
+ ```ruby
43
+ # Case-insensitive (default)
44
+ Article.international_search(title: "hello")
45
+ Article.international_search(title: "hello", description: "world")
46
+
47
+ # Case-sensitive
48
+ Article.international_search(title: "Hello", case_sensitive: true)
49
+
50
+ # With explicit locale
51
+ Article.international_search(title: "welt", locale: :de)
52
+ ```
53
+
54
+ ### international_order(attribute, direction = :asc, locale: nil)
55
+
56
+ Order by translated attribute:
57
+
58
+ ```ruby
59
+ Article.international_order(:title)
60
+ Article.international_order(:title, :desc)
61
+ Article.international_order(:title, :asc, locale: :de)
62
+ ```
63
+
64
+ ### translated(*attributes, locale: nil)
65
+
66
+ Find records with translations in specified locale:
67
+
68
+ ```ruby
69
+ Article.translated(:title)
70
+ Article.translated(:title, locale: :de)
71
+ Article.translated(:title, :description, locale: :de)
72
+ ```
73
+
74
+ ### untranslated(*attributes, locale: nil)
75
+
76
+ Find records missing translations:
77
+
78
+ ```ruby
79
+ Article.untranslated(:title)
80
+ Article.untranslated(:title, locale: :de)
81
+ ```
82
+
83
+ ## Chaining with ActiveRecord
84
+
85
+ All methods return `ActiveRecord::Relation`, so they chain naturally with AR methods:
86
+
87
+ ```ruby
88
+ Article.international(title: "Hello World")
89
+ .where(published: true)
90
+ .order(created_at: :desc)
91
+ .limit(10)
92
+ .includes(:author)
93
+ ```
94
+
95
+ ## Combining Multiple Queries
96
+
97
+ Use `merge` to combine multiple international queries:
98
+
99
+ ```ruby
100
+ # Search + filter + order
101
+ Article.international_search(title: "hello")
102
+ .merge(Article.international(status: "published"))
103
+ .merge(Article.international_order(:title, :desc))
104
+
105
+ # Query across multiple locales
106
+ Article.international(title: "Hello World", locale: :en)
107
+ .merge(Article.international(title: "Hallo Welt", locale: :de))
108
+ ```
109
+
110
+ ## Examples
111
+
112
+ ```ruby
113
+ # Find published articles with German title containing "Welt"
114
+ Article.international_search(title: "Welt", locale: :de)
115
+ .where(published: true)
116
+ .merge(Article.international_order(:title, locale: :de))
117
+ .limit(10)
118
+
119
+ # Find articles missing German translation
120
+ Article.untranslated(:title, locale: :de).count
121
+
122
+ # Complex multi-locale query - find articles with both EN and DE titles
123
+ Article.translated(:title, locale: :en)
124
+ .merge(Article.translated(:title, locale: :de))
125
+
126
+ # Exclude drafts and order by title
127
+ Article.international_not(status: "draft")
128
+ .merge(Article.international_order(:title))
129
+ ```
@@ -28,7 +28,6 @@ module Internationalize
28
28
  #
29
29
  # When called with Symbol arguments, declares attributes as internationalized:
30
30
  # international :title, :description
31
- # international :title, fallback: false
32
31
  #
33
32
  # When called with keyword arguments, queries translated attributes:
34
33
  # Article.international(title: "Hello") # exact match
@@ -37,16 +36,15 @@ module Internationalize
37
36
  # Article.international(title: "Hello", match: :partial, case_sensitive: true)
38
37
  #
39
38
  # @param attributes [Array<Symbol>] attributes to declare as internationalized
40
- # @param fallback [Boolean] whether to fallback to default locale (default: true)
41
39
  # @param locale [Symbol] locale to query (default: current locale)
42
40
  # @param match [Symbol] :exact or :partial (default: :exact)
43
41
  # @param case_sensitive [Boolean] for partial matching only (default: false)
44
42
  # @param conditions [Hash] attribute => value pairs to query
45
43
  #
46
- def international(*attributes, fallback: true, locale: nil, match: :exact, case_sensitive: false, **conditions)
44
+ def international(*attributes, locale: nil, match: :exact, case_sensitive: false, **conditions)
47
45
  if attributes.any? && attributes.first.is_a?(Symbol) && conditions.empty?
48
46
  # Declaration mode: international :title, :description
49
- declare_international_attributes(attributes, fallback: fallback)
47
+ declare_international_attributes(attributes)
50
48
  else
51
49
  # Query mode: Article.international(title: "Hello")
52
50
  international_query(conditions, locale: locale, match: match, case_sensitive: case_sensitive)
@@ -71,7 +69,7 @@ module Internationalize
71
69
  "Use standard ActiveRecord methods for non-translated attributes."
72
70
  end
73
71
 
74
- locale ||= Internationalize.locale
72
+ locale ||= I18n.locale
75
73
  direction = direction.to_s.upcase
76
74
  direction = "ASC" unless VALID_DIRECTIONS.include?(direction)
77
75
 
@@ -91,7 +89,7 @@ module Internationalize
91
89
  # Article.translated(:title, :description, locale: :de)
92
90
  #
93
91
  def translated(*attributes, locale: nil)
94
- locale ||= Internationalize.locale
92
+ locale ||= I18n.locale
95
93
  adapter = Adapters.resolve(connection)
96
94
  scope = all
97
95
 
@@ -117,7 +115,7 @@ module Internationalize
117
115
  # Article.untranslated(:title, locale: :de)
118
116
  #
119
117
  def untranslated(*attributes, locale: nil)
120
- locale ||= Internationalize.locale
118
+ locale ||= I18n.locale
121
119
  adapter = Adapters.resolve(connection)
122
120
  scope = all
123
121
 
@@ -143,7 +141,7 @@ module Internationalize
143
141
  # Article.international_not(title: "Entwurf", locale: :de)
144
142
  #
145
143
  def international_not(locale: nil, **conditions)
146
- locale ||= Internationalize.locale
144
+ locale ||= I18n.locale
147
145
  adapter = Adapters.resolve(connection)
148
146
  scope = all
149
147
 
@@ -233,12 +231,12 @@ module Internationalize
233
231
  end
234
232
 
235
233
  # Declares attributes as internationalized
236
- def declare_international_attributes(attributes, fallback:)
234
+ def declare_international_attributes(attributes)
237
235
  self.international_attributes = international_attributes | attributes.map(&:to_sym)
238
236
 
239
237
  attributes.each do |attr|
240
238
  warn_if_missing_default(attr)
241
- define_translation_accessors(attr, fallback: fallback)
239
+ define_translation_accessors(attr)
242
240
  define_locale_accessors(attr)
243
241
  end
244
242
  end
@@ -258,7 +256,7 @@ module Internationalize
258
256
 
259
257
  # Query translated attributes with exact or partial matching
260
258
  def international_query(conditions, locale:, match:, case_sensitive:)
261
- locale ||= Internationalize.locale
259
+ locale ||= I18n.locale
262
260
  adapter = Adapters.resolve(connection)
263
261
  scope = all
264
262
 
@@ -288,20 +286,19 @@ module Internationalize
288
286
  end
289
287
 
290
288
  # Defines the main getter/setter for an attribute
291
- def define_translation_accessors(attr, fallback:)
289
+ def define_translation_accessors(attr)
292
290
  translations_column = "#{attr}_translations"
293
291
 
294
- # Main getter - returns translation for current locale
295
- # Cache default locale at definition time for faster fallback
296
- default_locale_str = fallback ? Internationalize.default_locale.to_s : nil
292
+ # Main getter - returns translation for current locale with fallback to default locale
293
+ default_locale_str = I18n.default_locale.to_s
297
294
 
298
295
  define_method(attr) do |locale = nil|
299
296
  locale_str = (locale || I18n.locale).to_s
300
297
  translations = read_attribute(translations_column)
301
298
  value = translations[locale_str]
302
299
 
303
- # Short-circuit: return early if value exists or no fallback needed
304
- return value if !fallback || !value.nil? || locale_str == default_locale_str
300
+ # Short-circuit: return early if value exists or already querying default locale
301
+ return value if !value.nil? || locale_str == default_locale_str
305
302
 
306
303
  translations[default_locale_str]
307
304
  end
@@ -362,23 +359,10 @@ module Internationalize
362
359
 
363
360
  # Instance methods
364
361
 
365
- # Set translation for a specific locale
366
- def set_translation(attr, locale, value)
367
- column = "#{attr}_translations"
368
- translations = read_attribute(column)
369
- translations[locale.to_s] = value
370
- write_attribute(column, translations)
371
- end
372
-
373
- # Get translation for a specific locale without fallback
374
- def translation_for(attr, locale)
375
- translations = read_attribute("#{attr}_translations")
376
- translations[locale.to_s]
377
- end
378
-
379
362
  # Check if a translation exists for a specific locale
380
363
  def translated?(attr, locale)
381
- translation_for(attr, locale).present?
364
+ translations = read_attribute("#{attr}_translations")
365
+ translations[locale.to_s].present?
382
366
  end
383
367
 
384
368
  # Returns all locales that have a translation for an attribute
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Internationalize
4
+ # Adds internationalization support for ActionText rich text attributes
5
+ #
6
+ # @example
7
+ # class Article < ApplicationRecord
8
+ # include Internationalize::Model
9
+ # include Internationalize::RichText
10
+ # international_rich_text :content
11
+ # end
12
+ #
13
+ # article.content = "<p>Hello</p>" # Sets for current locale
14
+ # article.content # Gets for current locale (with fallback)
15
+ # article.content_de # Direct access to German content
16
+ #
17
+ module RichText
18
+ extend ActiveSupport::Concern
19
+
20
+ class_methods do
21
+ # Declares a rich text attribute as internationalized
22
+ #
23
+ # Creates a has_rich_text association for each available locale and
24
+ # provides locale-aware accessors.
25
+ #
26
+ # @param name [Symbol] the attribute name
27
+ #
28
+ def international_rich_text(name)
29
+ # Generate has_rich_text for each locale
30
+ Internationalize.locales.each do |locale|
31
+ rich_text_name = :"#{name}_#{locale}"
32
+ has_rich_text(rich_text_name)
33
+ end
34
+
35
+ default_locale_str = I18n.default_locale.to_s
36
+
37
+ # Main getter - returns rich text for current locale with fallback to default locale
38
+ define_method(name) do
39
+ locale_str = I18n.locale.to_s
40
+ rich_text = send(:"#{name}_#{locale_str}")
41
+
42
+ if rich_text.blank? && locale_str != default_locale_str
43
+ send(:"#{name}_#{default_locale_str}")
44
+ else
45
+ rich_text
46
+ end
47
+ end
48
+
49
+ # Main setter - sets rich text for current locale
50
+ define_method(:"#{name}=") do |value|
51
+ send(:"#{name}_#{I18n.locale}=", value)
52
+ end
53
+
54
+ # Predicate method
55
+ define_method(:"#{name}?") do
56
+ send(name).present?
57
+ end
58
+
59
+ # Check if translation exists for a specific locale
60
+ define_method(:"#{name}_translated?") do |locale|
61
+ send(:"#{name}_#{locale}").present?
62
+ end
63
+
64
+ # Get all locales that have this rich text
65
+ define_method(:"#{name}_translated_locales") do
66
+ Internationalize.locales.select do |locale|
67
+ send(:"#{name}_#{locale}").present?
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Internationalize
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -10,22 +10,12 @@ require_relative "internationalize/model"
10
10
  module Internationalize
11
11
  class << self
12
12
  # Configuration
13
- attr_accessor :fallback_locale, :available_locales
13
+ attr_accessor :available_locales
14
14
 
15
15
  def configure
16
16
  yield self
17
17
  end
18
18
 
19
- # Returns the current locale, defaulting to I18n.locale
20
- def locale
21
- I18n.locale
22
- end
23
-
24
- # Returns the default locale for fallbacks
25
- def default_locale
26
- fallback_locale || I18n.default_locale
27
- end
28
-
29
19
  # Returns available locales, defaulting to I18n.available_locales
30
20
  def locales
31
21
  available_locales || I18n.available_locales
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: internationalize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sampo Kuokkanen
@@ -49,6 +49,11 @@ files:
49
49
  - CHANGELOG.md
50
50
  - LICENSE.txt
51
51
  - README.md
52
+ - context/configuration.md
53
+ - context/getting-started.md
54
+ - context/index.yaml
55
+ - context/model-api.md
56
+ - context/query-api.md
52
57
  - lib/generators/internationalize/translation/templates/add_translations_migration.rb.erb
53
58
  - lib/generators/internationalize/translation_generator.rb
54
59
  - lib/internationalize.rb
@@ -58,6 +63,7 @@ files:
58
63
  - lib/internationalize/adapters/postgresql.rb
59
64
  - lib/internationalize/adapters/sqlite.rb
60
65
  - lib/internationalize/model.rb
66
+ - lib/internationalize/rich_text.rb
61
67
  - lib/internationalize/version.rb
62
68
  homepage: https://github.com/sampokuokkanen/internationalize
63
69
  licenses: