dm-is-localizable
Datamapper support for localization of (user entered) content in multilanguage applications
Schema
- one xxx_translations table for every translatable resource
- xxx_translations belongs_to the resource to translate
- xxx_translations belongs_to a locale
- properties to be translated are defined in xxx_translations
Advantages
- Proper normalization and referential integrity
- Easy to add a new language (add row to xxx_translations)
- Easy to query
- Columns keep their names
Disadvantages (not really if you think about it)
- One extra table for every resource that needs translations
Example definition of a localizable model
The plugin comes with a Locale
model that already got required for you. This means that the underlying storage will be created automatically when you run auto_migrate!
or auto_upgrade!
.
class Item
include DataMapper::Resource
property :id, Serial
translatable do
property :name, String
property :desc, String
end
end
The above Item
model will define and thus be able to DataMapper.auto_migrate!
the ItemTranslation
model. The naming convention used here is "#{ClassToBeLocalized.name}Translation"
.
Preliminary support for changing this is available by using the :model
option like so (note that this isn’t specced yet).
DataMapper::Model.translatable, :model => 'ItemLocalization' do
# ...
end
Furthermore, the above Item
will automatically have the following instance methods defined.
#item_translations_attributes
#item_translations_attributes=
# and handy aliases for the above
#translations_attributes
#translations_attributes=
These are generated by dm-accepts_nested_attributes and allow for easy manipulation of the translatable properties from say forms in a web application. For more information on working with nested attributes, have a look at the documentation at the README for dm-accepts_nested_attributes
Of course you can turn this behavior off by specifying the translatable, :accept_nested_attributes => false do .. end
The resulting model you get when calling Item.translatable { ... }
looks like this:
class ItemTranslation
include DataMapper::Resource
property :id, Serial
property :item_id, Integer, :required => true, :unique_index => :unique_locales
property :locale_tag, String, :required => true, :unique_index => :unique_locales
property :name, String
property :desc, String
validates_is_unique :locale_tag, :scope => :item_id
belongs_to :item
belongs_to :locale
end
Furthermore, the following API gets defined on the Item
class:
class Item
include DataMapper::Resource
property :id, Serial
translatable do
property :name, String
property :description, String
end
# -------------------------
# added by .translatable
# -------------------------
has n, :item_translations
has n, :locales, :through => :item_translations
# and a handy alias
alias :translations :item_translations
# method to access the i18n proxy for this model
def self.i18n
@i18n
end
# the proxy instance to delegate api calls to
def i18n
@i18n ||= I18n::Resource::Proxy.new(self)
end
# translates the :name property to the given locale
def name(locale_tag = DataMapper::I18n.default_locale_tag)
i18n.translate(:name, locale_tag)
end
# translates the :desc property to the given locale
def desc(locale_tag = DataMapper::I18n.default_locale_tag)
i18n.translate(:desc, locale_tag)
end
# ----------------------------------------
# added by dm-accepts_nested_attributes
# ----------------------------------------
def item_translations_attributes
# ...
end
def item_translations_attributes=(attributes_or_attributes_collection)
# ...
end
# and handy aliases for the above
alias :translations_attributes :item_translations_attributes
alias :translations_attributes= :item_translations_attributes
end
# ---------------------------------------------
# methods accessible via the Item.i18n proxy
# ---------------------------------------------
# helper method to get at ItemTranslation
Item.i18n.translation_model
# list all available locales for the translatable model
Item.i18n.available_locales
# returns all translatable properties of this resource
Item.i18n.translatable_properties
# ---------------------------------------------
# methods accessible via the Item#i18n proxy
# ---------------------------------------------
# list all available locales for this instance
item.i18n.available_locales
# translates the given attribute to the locale identified by the given locale_code
item.i18n.translate(attribute, locale_tag)
Inspired by (thx guys!)
- Neil Barnwell’s comment on the top voted answer to Schema for a multilocale database
- Gabi Solomon’s option (4) at this blog post on Multilocale database design approach
Copyright
Copyright © 2009 Martin Gamsjaeger (snusnu). See LICENSE for details.