internationalize 0.2.4 → 0.3.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 +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +59 -11
- data/context/configuration.md +8 -4
- data/context/model-api.md +54 -2
- data/lib/internationalize/model.rb +5 -2
- data/lib/internationalize/rich_text.rb +1 -1
- data/lib/internationalize/validations.rb +85 -0
- data/lib/internationalize/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0808602c10b51ee1884f04c5b9fd66d05381d07c0f86d284517dce7f02ebb6cd'
|
|
4
|
+
data.tar.gz: fe0c214e9c386b191d4e8f06e1ddee136d9e7a15b25a59d4dd0c9a4860a6589a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18cf19554d3187476b26505e555e7f181fb6c31edc100e054a2a96f8eafbdb09fa1ad424544973abc349a8f68c62ec0d5634216c6d4fd4cfd029221331d620ed
|
|
7
|
+
data.tar.gz: d998cbcd3ede8f8a1574e3adcec37ae3afb6216772de801ed0693199bbe70befe398f89a0ff001a66f149cc10f226ed5fd93721c44067e3ebd0ba62ad6e6643d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,14 @@ 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
|
-
## [
|
|
8
|
+
## [0.3.0] - 2024-12-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `validates_international` method for locale-specific validations:
|
|
13
|
+
- `uniqueness: true` - validates uniqueness per-locale (requires JSON column querying)
|
|
14
|
+
- `presence: { locales: [:en, :de] }` - requires translations for specific locales (useful for admin interfaces)
|
|
15
|
+
- Standard Rails validations (`validates :title, presence: true`) now work with virtual accessors
|
|
9
16
|
|
|
10
17
|
## [0.2.4] - 2024-11-29
|
|
11
18
|
|
data/README.md
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
# Internationalize
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/internationalize)
|
|
4
|
+
[](https://github.com/sampokuokkanen/internationalize/actions)
|
|
5
|
+
|
|
3
6
|
Lightweight, performant internationalization for Rails with JSON column storage.
|
|
4
7
|
|
|
5
8
|
## Why Internationalize?
|
|
6
9
|
|
|
7
10
|
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
11
|
|
|
9
|
-
Unlike Globalize
|
|
12
|
+
Unlike Globalize (separate translation tables) or Mobility (JSON backend is PostgreSQL-only), Internationalize stores translations inline using JSON columns with **full SQLite, PostgreSQL, and MySQL support**:
|
|
10
13
|
|
|
11
14
|
- **No JOINs** - translations live in the same table
|
|
12
15
|
- **No N+1 queries** - data is always loaded with the record
|
|
13
16
|
- **No backend overhead** - direct JSON column access, no abstraction layers
|
|
14
17
|
- **~50% less memory** - no per-instance backend objects or plugin chains
|
|
15
18
|
- **Direct method dispatch** - no `method_missing` overhead
|
|
16
|
-
- **
|
|
19
|
+
- **True multi-database JSON support** - SQLite 3.38+, PostgreSQL 9.4+, MySQL 8.0+
|
|
20
|
+
- **ActionText support** - internationalized rich text with attachments
|
|
17
21
|
- **Visible in schema.rb** - translated fields appear directly in your model's schema
|
|
18
22
|
|
|
23
|
+
> **Note:** Mobility's JSON/JSONB backends only work with PostgreSQL. For SQLite or MySQL, Mobility requires separate translation tables with JOINs. Internationalize provides JSON column querying across all three databases.
|
|
24
|
+
|
|
19
25
|
## Supported Databases
|
|
20
26
|
|
|
21
27
|
| Database | JSON Column | Query Syntax |
|
|
@@ -173,9 +179,46 @@ I18n.locale = :de
|
|
|
173
179
|
article.title # => "Hello" (falls back to :en)
|
|
174
180
|
```
|
|
175
181
|
|
|
182
|
+
### Validations
|
|
183
|
+
|
|
184
|
+
For most validations, use standard Rails validators—they work with the virtual accessor for the current locale:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
class Article < ApplicationRecord
|
|
188
|
+
include Internationalize::Model
|
|
189
|
+
international :title
|
|
190
|
+
|
|
191
|
+
# Standard Rails validations (recommended)
|
|
192
|
+
validates :title, presence: true
|
|
193
|
+
validates :title, length: { minimum: 3, maximum: 100 }
|
|
194
|
+
validates :title, format: { with: /\A[a-z0-9-]+\z/ }
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Use `validates_international` only when you need:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
class Article < ApplicationRecord
|
|
202
|
+
include Internationalize::Model
|
|
203
|
+
international :title
|
|
204
|
+
|
|
205
|
+
# Uniqueness per-locale (requires JSON column querying)
|
|
206
|
+
validates_international :title, uniqueness: true
|
|
207
|
+
|
|
208
|
+
# Multi-locale presence (for admin interfaces editing all translations at once)
|
|
209
|
+
validates_international :title, presence: { locales: [:en, :de] }
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Errors from `validates_international` are added to locale-specific keys:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
article.errors[:title_en] # => ["has already been taken"]
|
|
217
|
+
```
|
|
218
|
+
|
|
176
219
|
### ActionText Support
|
|
177
220
|
|
|
178
|
-
For rich text with attachments
|
|
221
|
+
For rich text with attachments (requires ActionText):
|
|
179
222
|
|
|
180
223
|
```ruby
|
|
181
224
|
class Article < ApplicationRecord
|
|
@@ -211,15 +254,20 @@ hello_world:
|
|
|
211
254
|
|
|
212
255
|
## Configuration
|
|
213
256
|
|
|
257
|
+
No configuration required. Internationalize uses your existing Rails I18n settings:
|
|
258
|
+
|
|
259
|
+
- **Locales**: `I18n.available_locales`
|
|
260
|
+
- **Fallback**: `I18n.default_locale`
|
|
261
|
+
|
|
262
|
+
To override locales (rarely needed):
|
|
263
|
+
|
|
214
264
|
```ruby
|
|
215
265
|
# config/initializers/internationalize.rb
|
|
216
266
|
Internationalize.configure do |config|
|
|
217
|
-
config.available_locales = [:en, :de, :fr]
|
|
267
|
+
config.available_locales = [:en, :de, :fr]
|
|
218
268
|
end
|
|
219
269
|
```
|
|
220
270
|
|
|
221
|
-
Fallback uses `I18n.default_locale` automatically.
|
|
222
|
-
|
|
223
271
|
## Performance Comparison
|
|
224
272
|
|
|
225
273
|
Benchmark with 1000 records, 2 translated attributes (title + body), 3 locales:
|
|
@@ -250,13 +298,13 @@ Benchmark with 1000 records, 2 translated attributes (title + body), 3 locales:
|
|
|
250
298
|
|
|
251
299
|
### When to consider Mobility
|
|
252
300
|
|
|
253
|
-
|
|
301
|
+
Consider [Mobility](https://github.com/shioyama/mobility) if:
|
|
254
302
|
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
303
|
+
- You need to add translations without modifying existing table schemas
|
|
304
|
+
- You're on PostgreSQL and want their JSON backend (note: PostgreSQL-only)
|
|
305
|
+
- You need the flexibility of multiple backend strategies
|
|
258
306
|
|
|
259
|
-
Mobility's table backend stores translations in separate tables with JOINs, which trades query performance for schema flexibility.
|
|
307
|
+
Mobility's table backend stores translations in separate tables with JOINs, which trades query performance for schema flexibility. Their JSON backend is PostgreSQL-only.
|
|
260
308
|
|
|
261
309
|
## License
|
|
262
310
|
|
data/context/configuration.md
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
# Internationalize: Configuration
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Zero Configuration
|
|
4
|
+
|
|
5
|
+
No configuration required. Internationalize uses your existing Rails I18n settings:
|
|
6
|
+
|
|
7
|
+
- `I18n.available_locales` - determines which locale accessors are generated
|
|
8
|
+
- `I18n.default_locale` - used for fallback when translation is missing
|
|
9
|
+
|
|
10
|
+
## Override Locales (Rarely Needed)
|
|
4
11
|
|
|
5
12
|
```ruby
|
|
6
13
|
# config/initializers/internationalize.rb
|
|
7
14
|
Internationalize.configure do |config|
|
|
8
|
-
# Override available locales (defaults to I18n.available_locales)
|
|
9
15
|
config.available_locales = [:en, :de, :fr, :es]
|
|
10
16
|
end
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
Fallback locale is always `I18n.default_locale`.
|
|
14
|
-
|
|
15
19
|
## Database Setup
|
|
16
20
|
|
|
17
21
|
Use the generator to create migrations:
|
data/context/model-api.md
CHANGED
|
@@ -80,14 +80,66 @@ I18n.locale = :de
|
|
|
80
80
|
article.title # => "Hello" (falls back to default locale)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
## Validations
|
|
84
|
+
|
|
85
|
+
For most validations, use standard Rails validators—they work with the virtual accessor:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
class Article < ApplicationRecord
|
|
89
|
+
include Internationalize::Model
|
|
90
|
+
international :title
|
|
91
|
+
|
|
92
|
+
# Standard Rails validations (recommended)
|
|
93
|
+
validates :title, presence: true
|
|
94
|
+
validates :title, length: { minimum: 3, maximum: 100 }
|
|
95
|
+
validates :title, format: { with: /\A[a-z]+\z/ }
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `validates_international` only for uniqueness or multi-locale presence:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
class Article < ApplicationRecord
|
|
103
|
+
include Internationalize::Model
|
|
104
|
+
international :title
|
|
105
|
+
|
|
106
|
+
# Uniqueness per-locale (requires JSON column querying)
|
|
107
|
+
validates_international :title, uniqueness: true
|
|
108
|
+
|
|
109
|
+
# Multi-locale presence (for admin interfaces editing all translations at once)
|
|
110
|
+
validates_international :title, presence: { locales: [:en, :de] }
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Validation Options
|
|
115
|
+
|
|
116
|
+
| Option | Description |
|
|
117
|
+
|--------|-------------|
|
|
118
|
+
| `uniqueness: true` | Validates uniqueness per-locale (current locale) |
|
|
119
|
+
| `presence: { locales: [:en, :de] }` | Requires translations for specific locales |
|
|
120
|
+
|
|
121
|
+
### Error Keys
|
|
122
|
+
|
|
123
|
+
Standard Rails validations add errors to the virtual attribute:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
article.errors[:title] # => ["can't be blank"]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`validates_international` adds errors to locale-specific keys:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
article.errors[:title_en] # => ["has already been taken"]
|
|
133
|
+
```
|
|
134
|
+
|
|
83
135
|
## ActionText Support
|
|
84
136
|
|
|
85
|
-
For rich text with attachments,
|
|
137
|
+
For rich text with attachments, include `Internationalize::RichText` and use `international_rich_text`:
|
|
86
138
|
|
|
87
139
|
```ruby
|
|
88
140
|
class Article < ApplicationRecord
|
|
89
141
|
include Internationalize::Model
|
|
90
|
-
include Internationalize::RichText
|
|
142
|
+
include Internationalize::RichText # Requires ActionText
|
|
91
143
|
|
|
92
144
|
international_rich_text :content
|
|
93
145
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "validations"
|
|
4
|
+
|
|
3
5
|
module Internationalize
|
|
4
6
|
# Mixin for ActiveRecord models to enable internationalization
|
|
5
7
|
#
|
|
@@ -16,6 +18,7 @@ module Internationalize
|
|
|
16
18
|
#
|
|
17
19
|
module Model
|
|
18
20
|
extend ActiveSupport::Concern
|
|
21
|
+
include Internationalize::Validations
|
|
19
22
|
|
|
20
23
|
VALID_DIRECTIONS = ["ASC", "DESC"].freeze
|
|
21
24
|
|
|
@@ -334,7 +337,7 @@ module Internationalize
|
|
|
334
337
|
hash.each_key do |key|
|
|
335
338
|
unless allowed_locales.include?(key.to_s)
|
|
336
339
|
raise ArgumentError, "Invalid locale '#{key}' for #{attr}_translations. " \
|
|
337
|
-
"Allowed locales: #{allowed_locales.join(
|
|
340
|
+
"Allowed locales: #{allowed_locales.join(", ")}"
|
|
338
341
|
end
|
|
339
342
|
end
|
|
340
343
|
|
|
@@ -351,7 +354,7 @@ module Internationalize
|
|
|
351
354
|
|
|
352
355
|
if locale_str.include?("-")
|
|
353
356
|
raise ArgumentError, "Locale '#{locale}' contains a hyphen which is invalid for Ruby method names. " \
|
|
354
|
-
"Use underscore format instead: :#{locale_str.tr(
|
|
357
|
+
"Use underscore format instead: :#{locale_str.tr("-", "_")}"
|
|
355
358
|
end
|
|
356
359
|
|
|
357
360
|
getter_method = :"#{attr}_#{locale}"
|
|
@@ -31,7 +31,7 @@ module Internationalize
|
|
|
31
31
|
locale_str = locale.to_s
|
|
32
32
|
if locale_str.include?("-")
|
|
33
33
|
raise ArgumentError, "Locale '#{locale}' contains a hyphen which is invalid for Ruby method names. " \
|
|
34
|
-
"Use underscore format instead: :#{locale_str.tr(
|
|
34
|
+
"Use underscore format instead: :#{locale_str.tr("-", "_")}"
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Internationalize
|
|
4
|
+
# Validation support for internationalized attributes
|
|
5
|
+
#
|
|
6
|
+
# For simple validations (presence, length, format), use standard Rails validations
|
|
7
|
+
# which work with the virtual accessor for the current locale:
|
|
8
|
+
#
|
|
9
|
+
# validates :title, presence: true
|
|
10
|
+
# validates :title, length: { minimum: 3 }
|
|
11
|
+
#
|
|
12
|
+
# Use validates_international for:
|
|
13
|
+
# - Uniqueness (requires JSON column querying)
|
|
14
|
+
# - Multi-locale presence (admin interfaces editing all translations at once)
|
|
15
|
+
#
|
|
16
|
+
# @example Uniqueness per-locale
|
|
17
|
+
# validates_international :title, uniqueness: true
|
|
18
|
+
#
|
|
19
|
+
# @example Require specific locales (admin/manager interfaces)
|
|
20
|
+
# validates_international :title, presence: { locales: [:en, :de] }
|
|
21
|
+
#
|
|
22
|
+
module Validations
|
|
23
|
+
extend ActiveSupport::Concern
|
|
24
|
+
|
|
25
|
+
class_methods do
|
|
26
|
+
# Validates internationalized attributes
|
|
27
|
+
#
|
|
28
|
+
# @param attrs [Array<Symbol>] attribute names to validate
|
|
29
|
+
# @param options [Hash] validation options
|
|
30
|
+
# @option options [Boolean] :uniqueness validate uniqueness per-locale (current locale)
|
|
31
|
+
# @option options [Hash] :presence with :locales array for multi-locale presence
|
|
32
|
+
#
|
|
33
|
+
# @example Uniqueness validation (per-locale)
|
|
34
|
+
# validates_international :title, uniqueness: true
|
|
35
|
+
#
|
|
36
|
+
# @example Multi-locale presence (for admin interfaces)
|
|
37
|
+
# validates_international :title, presence: { locales: [:en, :de] }
|
|
38
|
+
#
|
|
39
|
+
def validates_international(*attrs, **options)
|
|
40
|
+
presence_opts = options.delete(:presence)
|
|
41
|
+
uniqueness_opts = options.delete(:uniqueness)
|
|
42
|
+
|
|
43
|
+
attrs.each do |attr|
|
|
44
|
+
validate_international_presence(attr, presence_opts) if presence_opts
|
|
45
|
+
validate_international_uniqueness(attr, uniqueness_opts) if uniqueness_opts
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def validate_international_presence(attr, options)
|
|
52
|
+
unless options.is_a?(Hash) && options[:locales]
|
|
53
|
+
raise ArgumentError, "validates_international presence requires locales: option. " \
|
|
54
|
+
"For current locale, use: validates :#{attr}, presence: true"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
locales = options[:locales].map(&:to_s)
|
|
58
|
+
|
|
59
|
+
validate do |record|
|
|
60
|
+
locales.each do |locale|
|
|
61
|
+
value = record.send("#{attr}_#{locale}")
|
|
62
|
+
if value.blank?
|
|
63
|
+
record.errors.add("#{attr}_#{locale}", :blank)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_international_uniqueness(attr, _options)
|
|
70
|
+
validate do |record|
|
|
71
|
+
locale = I18n.locale.to_s
|
|
72
|
+
value = record.send("#{attr}_#{locale}")
|
|
73
|
+
next if value.blank?
|
|
74
|
+
|
|
75
|
+
scope = record.class.international(attr => value, locale: locale)
|
|
76
|
+
scope = scope.where.not(id: record.id) if record.persisted?
|
|
77
|
+
|
|
78
|
+
if scope.exists?
|
|
79
|
+
record.errors.add("#{attr}_#{locale}", :taken)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sampo Kuokkanen
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- lib/internationalize/adapters/sqlite.rb
|
|
65
65
|
- lib/internationalize/model.rb
|
|
66
66
|
- lib/internationalize/rich_text.rb
|
|
67
|
+
- lib/internationalize/validations.rb
|
|
67
68
|
- lib/internationalize/version.rb
|
|
68
69
|
homepage: https://github.com/sampokuokkanen/internationalize
|
|
69
70
|
licenses:
|