[Catalyst] Localization (was - Where should constraints go)

John Siracusa siracusa at mindspring.com
Sat Nov 4 14:23:04 GMT 2006


On 11/4/06 5:43 AM, Ian Docherty wrote:
>>    <% 'START_FORM' |fm %>
>>    <table>
>>    <tr>
>>    <td class="label"><% 'USERNAME:LABEL' |f %></td>
>>    <td class="field"><% 'USERNAME:FIELD' |f %></td>
>>    </tr>
>>    ...
> 1) The contents of USERNAME:LABEL and USERNAME:FIELD have to be created
> somewhere. I assume that this is in the controller, which creates some
> HTML (using widgets or the like) and which inserts the localised text?

No, what happens is the "USERNAME:LABEL" bits get translated (by the custom
"f" mason escape) into the method call:

    $ARGS{'form'}->field('username')->xhtml_label()

which generates the HTML at the time of the call (i.e., on demand inside the
template itself, not in the controller).  What gets passed into the template
is the form object (based on Rose::HTML::Form) which does not contain any
preexisting HTML.

The custom syntax is a bit of a distraction, but it basically works like
this:

    <form>:<field>:<method>[ name="value", ...]

* <form> is the name of the parameter that the form object is passed to the
template under.  It defaults to "form", in which case it can be omitted (as
it has been in the exmaples).

* <field> is the name of the field.

* <method> is a method name or a string that maps to a method name.  (I have
a small hash to map from "friendly" names to method names, e.g., LABEL =>
xhtml_label)

* Finally, optional name/value pairs can be passed so designers can
customize fields for special cases.  (In general, the field objects should
already be set up correctly before being passed to the form, but sometimes
tweaks are needed.)

All values are converted to lowercase; the uppercase is just there to make
the text stand out more so the designers can find it in the templates.

As for localization, that happens on demand as well.  At the time some HTML
is generated (by a method call inside the template) the appropriate
localized message is looked up and has its placeholders filled in, etc.  In
fact, you can even change locale mid-template with no problem:

    <!-- English label, field, and error msgs -->
    <tr>
    <td class="label"><% 'USERNAME:LABEL' |f %></td>
    <td class="field"><% 'USERNAME:FIELD' |f %></td>
    </tr>
 
    % $form->locale('fr');

    <!-- French label, field, and error msgs -->
    <tr>
    <td class="label"><% 'PASSWORD:LABEL' |f %></td>
    <td class="field"><% 'PASSWORD:FIELD' |f %></td>
    </tr>

> 2) I don't like the idea of there being *any* html code produced in the
> controller  since in principle  the same controller could be used for
> different output media.

I have a map of form names to form objects.  My controllers request a form
object by name (e.g., "prepare the 'login_form'") which causes a form object
of the appropriate class to be constructed (well, it's usually cached) and
initialized.  Then I pass that form object to the view.

> 3) ISTM that there should be another layer between the controller and
> the Template to perform the localisation.

In my setting, we have a preexisting system for "static" localization (in 16
different languages no less) that happens when a site is "built."  I've had
to create my own system for "runtime" localization so that I can pass the
same form object to any template and it will be aware of the current locale
and produce strings (looked up by message id, yada yada) in the correct
language.  This code is actually on CPAN in Rose::HTML::Objects, but is not
yet documented because I'm still wringing out the API at work.  You can look
at the "*l10n*.t" and other tests to see some examples, however.

> Perhaps this is where the View needs to actually have code rather than taking
> the passive approach that I see in all the Catalyst examples. So the
> controller would generate some generic error flag (e.g. a flag to warn that
> the username field is already taken, or is too long) and the localisation
> 'layer' would convert this into the appropriate language version.

That's just what my form objects do.  My errors are opaque ids (represented
by constants in Perl land) which the forms map to appropriate error
messages.  So, for example, the login action:

    my $form = $self->prepare_form('login_form');
    ...
    unless($user->login)
    {
      $form->error_id($user->error_id);
      # Show login page, passing $form
      ...
    }

The form object then looks up the appropriate message_for_error_id(...)
(which, by default, is the message with the same id as the error) when it's
asked to produce an error message.

-John





More information about the Catalyst mailing list