Active Gibberish

- 3 mins read

UPDATE: you don’t need this code, because starting from the 2.2 version of Rails, localization support is built-in.

Localization for Active Record error messages

Today i had to answer to one of the questions every non-english Rails developer stumbles upon now or after.. how to localize AR error messages for pleasant appearance to a non-english customer ;).

First off, thanks to defunkt’s excellent gibberish plugin and to the way AR validation errors are exposed, the task was accomplished in an easy and clean manner, without messing too much with AR’s internals.

I started by translating every default AR error message, with this translation file located in lang/it.yml:

# Active Record errors
#
ar_accepted:     "deve essere accettato" 
ar_not_a_number: "non è un numero" 
ar_blank:        "è un campo obbligatorio" 
ar_empty:        "è un campo obbligatorio" 
ar_inclusion:    "non è nella lista dei valori validi" 
ar_too_long:     "è troppo lungo (massimo %d caratteri)" 
ar_exclusion:    "è riservato" 
ar_too_short:    "è troppo corto (minimo %d caratteri)" 
ar_invalid:      "non è valido" 
ar_wrong_length: "è errato, dovrebbe essere di %d caratteri" 
ar_confirmation: "non corrisponde" 
ar_taken:        "esiste già" 
# This one is not a default key, but I use it in my validations
ar_greater_zero: "deve essere maggiore di zero" 

and four lines in config/environment.rb:

Gibberish.current_language = :it
ActiveRecord::Errors.default_error_messages =
  ActiveRecord::Errors.default_error_messages.inject({}) {|h, (key, string)|
    h.update(key => string["ar_#{key}".intern]) # <em>Gibberish magic</em>
}

The first one simply sets Italian (:it) as the default language, the inject builds a new error_messages hash using Gibberish to translate the default ones. I named every AR error key in my translation file with an “ar_” prefix, in order to avoid possible future key clashes. Finally, AR array is overwritten with the new one freshly built.

This solution assumes that the application will show only in a single language, if you need localized error messages in different language you should put this code in some around_filter, like Gibberish documentation suggests.

OK, messages are translated, but what about field names? I used english field names in my tables, how about translating them as well? First, we need the actual translations in lang/it.yml:

# Field names
#
fld_multiplier:  "La dimensione" 
fld_service_id:  "Il servizio" 
fld_customer_id: "Il contraente" 
fld_address_id:  "L'indirizzo" 
fld_user_id:     "L'utente" 
fld_city_id:     "La città" 
fld_state_id:    "La provincia" 
fld_address:     "L'indirizzo" 
fld_terms_of_service: "Il testo di informativa sulla privacy" 

And then a customized helper that our view will call, loosely based on the default error_messages_for Rails helper:

  # error messages localization
  # uses gibberish.
  def foo_object_error_messages
    return '' if @foo_object.errors.count.zero?
    header_message = "Si prega di correggere i seguenti errori:" 
    error_messages = @foo_objects.errors.map do |field, err|
      field = field["fld_#{field}".intern] # [i]Gibberish magic[/i]
      content_tag(:li, "#{field} #{err}")
    end 
    content_tag(:div,
      content_tag(:h2, header_message) <<
        content_tag(:ul, error_messages),
      :class => 'errorExplanation',
      :id    => 'errorExplanation'
    )   
  end 

Here, easily, every field name is substituted by its translated counterpart, and a div is built with the plain vanilla class and id used by Rails’ [code]scaffold.css[/code], so that I won’t have to write a single line of CSS.

In your view: <%= foo_object_error_messages %>

This helper could be of course improved, but I’m a bit lazy right now.

Have fun! :D

Five minutes ago, I overwritten the super-shining-new CSS stylesheet that implements the current color scheme, because i wanted to restore the original one and put it in a new theme for this site, so that people who enjoyed the old theme could continue to use it. But, as the most kiddiest system administrator, i uncompressed the original files from the backup archive OVER the current ones..

Safari to the rescue! Every cached item by safari is stored into a SQlite3 database located in ~/Library/Caches/com.apple.Safari, let’s inspect how it is structured:

 13:54:42 vjt@voyager:~/Library/Caches/com.apple.Safari$ sqlite3 Cache.db 
SQLite version 3.5.1
Enter ".help" for instructions

sqlite> .tables
cfurl_cache_blob_data       cfurl_cache_schema_version
cfurl_cache_response      

sqlite> .schema cfurl_cache_response 
CREATE TABLE cfurl_cache_response(
  entry_ID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  version INTEGER,
  hash_value INTEGER,
  storage_policy INTEGER,
  request_key TEXT UNIQUE,
  time_stamp NOT NULL DEFAULT CURRENT_TIMESTAMP);

sqlite> .schema cfurl_cache_blob_data
CREATE TABLE cfurl_cache_blob_data(
  entry_ID INTEGER PRIMARY KEY,
  response_object BLOB,
  request_object BLOB,
  receiver_data BLOB,
  proto_props BLOB,
  user_info BLOB);

sqlite> select * from cfurl_cache_response limit 3;
1|0|1897220634|0|http://..../|2008-01-19 11:10:33
2|0|-662909776|0|http://..../|2008-01-19 11:10:33

Wow. Impressive. That’s why i love Apple products, because they are so well structured that you can freely inspect them and use them and their resources for every unplanned task you could have to complete.. even to fix your own mistakes ;). And it’s also intriguing, because you have to scratch your own itch and find the solution while exploring a beautifully constructed software product.

To make a long story short, every cached URL is stored into the request_key field of the cfurl_cache_response table, while in the receiver_data field of the cfurl_cache_blob_data there is the actual cached data. Now we can look for the overwritten bbs theme CSS stylesheet:

sqlite> select entry_ID, request_key from cfurl_cache_response
   ...> where request_key like '%bbs/style.css';
??1950??|http://sindro.me/sites/all/themes/bbs/style.css

Now, let’s search in the blob_data table the entry with ID 1950:

sqlite> select receiver_data from cfurl_cache_blob_data
   ...> where entry_ID = 1950;
/**
 * Themetastic, for Drupal 5.0
 * Stefan Nagtegaal, iStyledThis [dot] nl
 * Steven Wittens, acko [dot] net`
 *
 * If you use a customized color scheme, you must regenerate it after
 * modifying this file.
[......rest of the stylesheet removed.....]

YAY! Found it! A quick cut&paste.. and the lost theme is back! :D

Today's row

- 1 min read
05:01:24 vjt@voyager:~/Antani/trunk$ replace(){ sed -e "s|$1|$2|g" 
< $3 > ${3}X; mv ${3}X $3; }; egrep -r 'XP_[A-Z_]+[[:space:]]+-?[[
:digit:]]' Headers |ruby -ne "f,m=scan(/(.+):.+(XP_[\w_]+)/).first
;puts '%s %s %s' % [ f, m, 'kXP'<<m.scan(/(_[A-Z])([A-Z]+)/).map {
|a,b| a[1..1]<<b. downcase }.join ]" | while read hdr from to; do
replace $from $to $hdr; for src in `grep -rl $from Sources`; do
replace $from $to $src; done; done
  • You must have PTH installed, and maybe other libs.
  • This was tested on SCO_SV os507 3.2 5.0.7 i386

If you have UDK, run:

$ CFLAGS='-I/usr/local/include -belf' LDFLAGS='-L/usr/local/lib' \
  ./configure --with-threads --with-pth --disable-shared --disable-ipv6
  • Add /usr/local/include to BASECFLAGS in Makefile (autocrap sucks).
  • Patch Modules/ctypes/_ctypes_test.c by putting an #ifdef HAVE_LONG_LONG around functions that use PY_LONG_LONG (hints: lines 384 and 318).
  • Patch Objects/longobject.c and on line 817 put the IS_LITTLE_ENDIAN macro before the #ifdef HAVE_LONG_LONG block, and put _PyLong_FromSsize_t and _PyLong_FromSize_t after the HAVE_LONG_LONG block.

If you have GCC, run:

$ CFLAGS='-I/usr/local/include' LDFLAGS='-L/usr/local/lib'            \
  ./configure --with-threads --with-pth --disable-shared --disable-ipv6

Either with UDK or GCC:

  • Edit pyconfig.h and comment out the socklen_t define
  • Edit Modules/socketmodule.c and on line 226 add || defined(SCO5) in order to define INET_ADDRSTRLEN.
  • Run make (or gmake if you wish)
  • You will be left without _curses.so, _curses_panel.so, _locale.so and readline.so if using GCC and also pyexpat, elementtree and sha512 if using UDK.
      __   ____  __ __  ____     __
      \ \ / /  \/  |  \/  \ \   / /
       \ V /| |\/| | |\/| |\ \ / / 
        | | | |  | | |  | | \ V /_ 
        |_| |_|  |_|_|  |_|  \_/(_)
[vjt@os507 ~/Python-2.5.1-vjt] $ python
Python 2.5.1 (r251:31337, Sep 13 2007, 22:40:33) 
[GCC 4.2.1] on sco_sv3
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> 
[vjt@os507 ~] $ hg clone http://code.wuhrer.thc/hg/Antani
destination directory: Antani
http authorization required

!! YAY! :D