http://groups.google.com/group/rails-patch-i18n/web/overall-goal-and-features-summary?hl=en
Also, I've tried to sum up what open questions remain, see below.
My personal opinion is to go with:
- Simple backend (see below), because it seems to make things more
clear overall
- clearly: Strings as dot-separated keys
- no positional interpolation arguments
# Slim backend vs. clean client code
Now, there are a couple of options regarding how much logic the
backend that
Rails ships with implements itself. This question basically boils down
to a
tradeoff between: Simplicity of the backend in Rails on the one hand and
cleaner/dryer Rails client code (e.g. in a couple of helpers,
ActiveRecord
etc.)
Right now there are two experimental backends which stand for both
options:
I18n::Backend::Minimal
This backend only implements an #interpolate method and completely omits
lookup (i.e. it relies on a being given a default string) and
pluralization.
An exemplary piece of client code would look like this (from
distance_of_time_in_words):
locale.t :"datetime.distance_in_words.x_minutes", :count => minutes,
:default => "{{count}} minutes"
This could (and maybe should, if we decide to go down this route) be
taken
even further by also omitting the #interpolate method from the backend:
locale.t(:"datetime.distance_in_words.x_minutes", :count =>
minutes) ||
"#{minutes} minutes"
The advantages of this approach is that the backend that Rails ships
with is
kept minimal. The translations are stored with the client code, so we
don't
need to bother with implementing dictionaries for the Rails libraries.
Also
it is possible to omit pluralization and/or interpolation logic. It
would be
"just a hook".
The disadvantage is that the client code gets much more wordy and
repitive.
Also, it could be seen as a disadvantage that the default translations
are
spread throughout the Rails code instead of having them collected and
visible
in one place. (There aren't too many strings though, so maybe this
doesn't
really count.)
I18n::Backend::Simple
This backend implements all of the features in the simplest way
possible and
tailored for English-only (and comparably simple grammars).
Doing so it allows the client code to look like this:
locale.t :"datetime.distance_in_words.x_minutes", :count => minutes
So we'd have much cleaner client code and a more complete backend
implementation. The Simple backend could out of the box be used for
languages
that have a grammar like English (like no fancy pluralization rules)
and could
be adapted for most languages with some simple modifications. It is
still
pretty lightweight though, providing just the basics.
So, what do we want? A slim barebone backend at the expense of more
wordy
client code? Or a simple backend implementation at the expense of
shipping a
minimal I18n implementation with Rails? This obviously is not a
technical
question.
# Keys as Arrays vs dot-separated Symbols or Strings
Previously we have agreed on an api where #translate would take a
single main
key and a couple of additional keys as a scope. The api looked like
this:
translate :precision, locale, :scope => [:currency, :format]
This doesn't read very well though because the main key is pulled to
the left.
Also, the explicit :scope parameter is a bit wordy and takes an extra
of 12
characters per method call.
Thus, the idea came up to pull the keys together to a dot-separated
Symbol:
translate :'currency.format.precision', locale
(Obviously if this looks to Java'ish, a different character could be
used like
a colon, dash or slash.)
The Symbol notation looks a bit strange though. So far we still use a
Symbol
everywhere because we initially wanted to reserve String#translate for
default
translations. By now there seems to be a consensus that the old
Globalize way
of using the default string as a key is not an option. Thus, we could
also
allow to use a String as a key:
translate 'currency.format.precision', locale
(Personally I like this because think it doesn't get any more clear
and terse.)
# Generic mixins vs. explicit methods
So far the I18n library implements the Localization and Translation
modules
which are meant as generic mixins to be used in various client classes
(like
String, Date, Time, ...).
To adapt the method signature for these client classes it passes the
arguments
(previously only options) to #typify_localization_args which can be
implemented in the client class.
While this works fine I suggest to drop this abstraction, instead
implement
#translate and #localize respectively in the various classes, do the
necessary
argument munging and relay to I18n::translate ... or wherever.
The advantage is that we than have an explicit #translate method in
each of
the client classes so that it can be documented accordingly.
# Interpolation with explicit vs. positional variables
I'm not sure if this is an issue but I'll list it for completeness.
So far we've agreed on explicit interpolation variable names like in:
interpolate 'Hi {{name}}', :name => 'David'
Now, Rails itself and maybe thousands of users use Strings like this for
their ActiveRecord validations and stuff like that:
"is too long (maximum is %d characters)"
Thus the question came up whether we should also have an optional way of
interpolating positional arguments to such a string.
Personally, looking at the additional complexity that would need to be
added
to the api, I don't think it's worth it. But maybe I'm missing
something.
# The Locale class
(This is not an open question, but wasn't in I18n previously, so I
include
it for your interest and discussion.)
There are, in a way, two "styles" of using the #translate api. One
assumes
that the locale is known to the client code and can be passed down to
the
backend. This is certainly the more explicit and safe way to do it.
The other
way assumes that the locale is set somewhere globally. Previously,
quite some
plugins did that by setting a class variable on class Locale or similar.
Our api and Rails helpers implementation actually allows to use the
former
approach - except for ActiveRecord validations (which haven't even been
implemented yet for that reason) - and maybe our longterm goal should
be to
change ActiveRecord in a way so that model instances can carry a
locale and
pass it to #translate when generating an error message. That would be
quite
some additional work though and we should have at least an intermediary
solution.
Rails itself uses the Thread.current hash in quite some places to
store global
information in a threadsafe manner. Amongst others it uses this for
setting
the current Timezone which obviously is something pretty similar to our
current Locale.
Thus, we've implemented a Locale class that allows to globally set the
current
locale. The backend can (and the Simple backen will) check this place
if no
locale has been passed as an argument. So we can now do this:
Locale.use 'fr-FR'
'active_record.error_messages.too_long'.t # in ActiveRecord
--
sven fuchs sven...@artweb-design.de
artweb design http://www.artweb-design.de
grünberger 65 + 49 (0) 30 - 47 98 69 96 (phone)
d-10245 berlin + 49 (0) 171 - 35 20 38 4 (mobile)
---
http://groups.google.com/group/rails-patch-i18n/web/overall-goal-and-features-summary?hl=en
I18n::Backend::Minimal
I18n::Backend::Simple
translate :'currency.format.precision', locale
translate 'currency.format.precision', locale
(Personally I really like this because think it doesn't get any more
clear and terse.)
# The Locale class
There are, in a way, two "styles" of using the #translate api. One
assumes that the locale is known to the client code and can be passed
down to the backend. This is certainly the more explicit and safe way
to do it. The other way assumes that the locale is set somewhere
globally. Previously, quite some plugins did that by setting a class
variable on class Locale or similar.
Our api and Rails helpers implementation actually allows to use the
former approach - except for ActiveRecord validations (which haven't
even been implemented yet for that reason) - and maybe our longterm
goal should be to change ActiveRecord in a way so that model instances
can carry a locale and pass it to #translate when generating an error
message. That would be quite some additional work though and we should
have at least an intermediary solution.
Rails itself uses the Thread.current hash in quite some places to
store global information in a threadsafe manner. Amongst others it
uses this for setting the current Timezone which obviously is
something pretty similar to our current Locale.
Thus, we've implemented a Locale class that allows to globally set the
current locale. The backend can (and the Simple backen will) check
this place if no locale has been passed as an argument. So we can now
do this:
Locale.use 'fr-FR'
'active_record.error_messages.too_long'.t # in ActiveRecord
> My personal opinion is to go with:
>
> - Simple backend (see below), because it seems to make things more
> clear overall
+1
> - clearly: Strings as dot-separated keys
+1, basically to_sym should be called on the key argument in the
translate code, so it would accept either Strings or Symbols. Not sure
if I prefer '.' or ':'.
> - no positional interpolation arguments
+1
> Locale.use 'fr-FR'
> 'active_record.error_messages.too_long'.t # in ActiveRecord
+1, although I'm not sure about the Locale class. Maybe this should be
part of the I18N module.
> While this works fine I suggest to drop this abstraction, instead
> implement #translate and #localize respectively in the various
> classes, do the necessary argument munging and relay to
> I18n::translate ... or wherever.
>
> The advantage is that we than have an explicit #translate method in
> each of the client classes so that it can be documented accordingly.
+1, I think. Also, I'm starting to think we should just settle on
.translate/.t for everything, even though you can't "translate" a
date. It really clears things up, and I think it's close enough to the
truth. My second preference would be to just use .localize/.l for
everything.
# Slim backend vs. clean client code
I'm in favor of the simple backend because it keeps everything cleaner.
# Keys as Arrays vs dot-separated Symbols or Strings
Dot-separated keys are easier to write and simpler to understand. Great
idea. I also agree on using strings for the keys since there's no real
point in always typing :'scope.key'. However symbols are still useful
for simple non-scoped key (e.g. :key) so why not just convert this
positional argument to a symbol or string (whatever we want to use
internally).
Just another quick idea: It would also be possible to map the unknown
method calls of the locale object to key names. So a call like this
locale.t :"datetime.distance_in_words.x_minutes", :count => minutes
looks like this
locale.datetime.distance_in_words.x_minutes :count => minutes
Would make things even clearer and allows very convenient scoping
(someway like Builder):
locale.datetime.distance_in_words do
locale.x_minutes :count => minutes
end
It's just another idea. Feels somewhat strange but it might deserve a
second thought. It's very easy and eliminates most of the noise but it
requires to have the locale object around (maybe #t/#l could return the
current locale). However implementation might become complex and it's
quite late for such a change so this stuff might be better placed in a
plugin.
# The Locale class
+1 for storing the locale somewhere globally.
# Generic mixins vs. explicit methods
Also +1 for dropping the mixin stuff.
# Interpolation with explicit vs. positional variables
Many i18n plugins are already using name based interpolation. I don't
think it's worth the effort to support format style interpolation.
Happy programming
Stephan Soller
Sven Fuchs schrieb:
Personally I like the parallelism of this with the Timezone support.
So it's not I18n.timezone (like I18n.current_locale) but Time.zone
(like Locale.current).
I feel it reads very well.
I18n.use_locale 'de-DE'
I18n.current_locale
Locale.use 'de-DE'
Locale.current
But that's certainly a matter of taste. What would be the advantage of
putting this in the I18n module from your perspective?
>
>> While this works fine I suggest to drop this abstraction, instead
>> implement #translate and #localize respectively in the various
>> classes, do the necessary argument munging and relay to
>> I18n::translate ... or wherever.
>>
>> The advantage is that we than have an explicit #translate method in
>> each of the client classes so that it can be documented accordingly.
> +1, I think. Also, I'm starting to think we should just settle on
> .translate/.t for everything, even though you can't "translate" a
> date. It really clears things up, and I think it's close enough to the
> truth. My second preference would be to just use .localize/.l for
> everything.
Hehe, we should have taken notes about how often we've gone back and
forth between all these variants.
I believe that latest "consensus" from the IRC meetings was to use #l
and #localize for everything. I've used #t and #translate because
that's what was in the I18n lib.
Personally I tend to agree with you. At least in German you have a
more general meaning of "translate" which applies to all sorts of
things where something is "translated" to something else. There is, in
a technical context at least, no problem with "translating" a Date or
a Time to a certain format. So from that (German) perspective
Date#translate wouldn't even be wrong.
Interesting ideas!
I'd say, let's experiment with that kind stuff in our plugins :)