<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Colin Knox : Writings from The Killswitch Collective</title>
    <link>http://www.killswitchcollective.com/blog/index.xml</link>
    <description>These articles were written by Colin Knox for Perspectives, the blog of The Killswitch Collective, a Chicago-based web development, design and communication firm.</description>
    <language>en-us</language>
    <item>
      <title>Automatic Admin Interface with ActsAsAdminable</title>
      <category>Development</category>
      <description>&lt;p&gt;&lt;a href="http://github.com/ColinK/acts_as_adminable/tree/master"&gt;Just get ActsAsAdminable now!&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Admin Interfaces&lt;/h3&gt;

&lt;p&gt;I was recently working on a site for a client, which was just six simple pages and a CMS in the back-end to administrate content.   Our CMS let you add and edit the FAQs on the FAQ page, add/remove downloads on the "Resources" page, etc.  I took it to the client and his first question was, "How do I edit the footer?".  After discussing, it turned out the client needed the ability to edit any piece of text on the entire site, which was very rich in detail.  Every element on the page had a different layout or position, and so on.  It's a very graphically complex site!&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; have extended the admin interface to support this.  But it wouldn't have fit well with the style of the rest of the admin interface. What would I call that page, "Scattered Bits of Miscellaneous Text"?  Something simpler, cleaner and more intuitive was needed.&lt;/p&gt;

&lt;h3&gt;Enter the Plugin&lt;/h3&gt;

&lt;p&gt;I searched for a plugin that would solve my problem but didn't find anything that exactly fit my situation, and I didn't look TOO hard because I was already excited at the prospect of writing my own. Long story short, I did, and it's called &lt;code&gt;acts_as_adminable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using it couldn't be simpler.   After you install it (or run &lt;code&gt;script/runner vendor/plugins/acts_as_adminable/install.rb&lt;/code&gt; if you got it via &lt;code&gt;git clone&lt;/code&gt;), you just go into your view, find the bit of text you want to replace, and turn it into a &lt;code&gt;content_tag&lt;/code&gt; with a &lt;code&gt;:key&lt;/code&gt; attribute &amp;mdash; and that's it! &lt;/p&gt;

&lt;h3&gt;An Example&lt;/h3&gt;

&lt;p&gt;Let's say you have code like this:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;div class="header"&amp;gt;
  &amp;lt;h1&amp;gt;At Company Inc., our mission is to meet your needs&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;

&lt;p&gt;To administrate this with &lt;code&gt;ActsAsAdminable&lt;/code&gt;, first rewrite it like so:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;div class="header"&amp;gt;
  &amp;lt;%= &lt;span class="ident"&gt;content_tag&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:h1&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;At Company Inc., our mission is to meet your needs&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:key&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;mission_statement&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt; %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;

&lt;p&gt;At this point your page should look exactly the same as it did a minute ago; &lt;code&gt;ActsAsAdminable&lt;/code&gt; is designed to be easily removed or disabled. Now you just need to tell your controller to make this page adminable:&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;PagesController&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="ident"&gt;acts_as_adminable&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:if=&lt;/span&gt;&lt;span class="punct"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Proc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="ident"&gt;session&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:current_user&lt;/span&gt;&lt;span class="punct"&gt;].&lt;/span&gt;&lt;span class="ident"&gt;is_admin?&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;if&lt;/code&gt; parameter is a code snippet that you customize to match whatever you use for authentication.  After all, you don't want just &lt;em&gt;anybody&lt;/em&gt; to be able to edit your page!&lt;/p&gt;

&lt;h3&gt;Results&lt;/h3&gt;

&lt;p&gt;Now when you visit the page, you get a visual clue on mousing over any admin-enabled element.  And a single click pops up an Ajax control for editing its contents! Any changes you make will be reflected instantly and for all subsequent visitors.  There are only a few caveats:&lt;/p&gt;

&lt;ol style="padding-left: 40px; list-style-type: decimal;"&gt;
&lt;li&gt;The &lt;code&gt;:key&lt;/code&gt; argument must be a globally unique string.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;content_tag&lt;/code&gt; should also have a unique &lt;code&gt;id&lt;/code&gt; HTML attribute, although by default &lt;code&gt;ActsAsAdminable&lt;/code&gt; will use the &lt;code&gt;:key&lt;/code&gt; for that purpose if you didn't specify your own.  So also make sure that you either specify a unique "id" HTML attribute, or that the ":key" does not collide with any other element's "id" attribute.&lt;/li&gt;
&lt;li&gt;The edit field uses markdown for formatting, so &lt;a href="http://daringfireball.net/projects/markdown/basics"&gt;read up&lt;/a&gt; if you're not familiar with its syntax.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that you know what to do with it, head over to &lt;a href="http://github.com/ColinK/acts_as_adminable/tree/master"&gt;github&lt;/a&gt; and simplify your site!&lt;/p&gt;
</description>
      <pubDate>Mon, 26 Jan 2009 12:45:25 -0800</pubDate>
      <link>http://www.killswitchcollective.com/articles/51</link>
      <guid>http://www.killswitchcollective.com/articles/51</guid>
    </item>
    <item>
      <title>Long-Running Processes in Rails with Spawn</title>
      <category>Development</category>
      <description>&lt;p&gt;I was working on a large community-driven website recently, which had always had the requirement to synchronize its user database with another (3rd party) site that we didn't control.   The most we could get out of the other site was for them to send us regular XML dumps of the changes (additions, removals, deletions).    Predictably, we wrote that as a rake task and added it to cron. &lt;/p&gt;

&lt;pre&gt;
@hourly   cd /apps/product/current &amp;&amp; export RAILS_ENV=production &amp;&amp; rake product:synchronize_database
&lt;/pre&gt;

&lt;p&gt;It worked great, until the client threw in ONE additional little snag; not only should it check for updates every hour, but an admin should be able to log in to the site and request an immediate synchronization.  Unfortunately, this task can take anywhere from 10 minutes to 10 hours, depending on the size of the XML file and the number of employees involved.  Clearly not a job for a simple backtick or &lt;code&gt;%x{}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are a number of different options for running background processes in Rails, but since I had pretty simple requirements (and needed quick results) the best choice for me was clearly &lt;code&gt;Spawn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can install the plugin from rubyforge:&lt;/p&gt;

&lt;pre&gt;
script/plugin install http://spawn.rubyforge.org/svn/spawn/
&lt;/pre&gt;

&lt;p&gt;Then implementing the client's request was as simple as adding a button with a &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html#M001635"&gt;remote_function&lt;/a&gt; and a controller action with:&lt;/p&gt;

&lt;pre&gt;
&lt;span class="ident"&gt;spawn&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:nice&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="number"&gt;7&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span style="color:#CC7833"&gt;do&lt;/span&gt;
        &lt;span class="ident"&gt;exec&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;cd &lt;span class="expr"&gt;#{RAILS_ROOT}&lt;/span&gt; &amp;amp;&amp;amp; export RAILS_ENV=&lt;span class="expr"&gt;#{RAILS_ENV}&lt;/span&gt; &amp;amp;&amp;amp; rake product:synchronize_database&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;:nice&lt;/code&gt; option (vital, in my case!) will make sure that your process doesn't monopolize the CPU (just like its &lt;a href="http://en.wikipedia.org/wiki/Nice_(Unix)"&gt;shell counterpart&lt;/a&gt;).   There are only a couple of &lt;a href="http://spawn.rubyforge.org/svn/spawn/README"&gt;other options&lt;/a&gt;; you can choose to fork or thread your process (fork is the default), and you can wait for it to finish.  Neither was necessary in my case.   Problem solved and, in typical Rails fashion, it only took a few lines of code!&lt;/p&gt;</description>
      <pubDate>Fri, 19 Sep 2008 12:52:34 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/35</link>
      <guid>http://www.killswitchcollective.com/articles/35</guid>
    </item>
    <item>
      <title>Letting Google Globalize Your Rails App</title>
      <category>Development</category>
      <description>&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/articles/globalize_with_google.zip"&gt;Just give me the globalize_with_google plugin now!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Anybody who's looked into localizing or internationalizing a Rails app has probably come across the "Globalize" plugin.  It's a bit of an 800 lb. gorilla in the sense that it supports potentially hundreds of languages, automatic generation of validation messages, and even multiple pluralization cases based on the exact number of objects being counted.  (There's a story about a people whose language only had three numbers- 1, 2, and 'many'.  Globalize can handle that!)  But as long as installation is as easy as "script/plugin install ...", who cares how much the gorilla weighs?&lt;/p&gt;

&lt;p&gt;On a related note, Google recently released a series of &lt;a href="http://code.google.com/apis/ajax/"&gt;AJAX APIs&lt;/a&gt; that are dead-simple to plug in to any web app, including one that does &lt;a href="http://code.google.com/apis/ajaxlanguage/"&gt;automatic translation&lt;/a&gt;.  Can you guess where I'm going with this?  &lt;/p&gt;

&lt;p&gt;As soon as I saw Google's announcement that they were offering a free translation API, I started thinking about how to write a plugin that used it to initialize a Globalize database.&lt;/p&gt;

&lt;p&gt;My solution, as sketched on the back of a napkin, had two pieces: The first would override Globalize's "String.translate" method. The other one would cache the translations so we still had a checklist of phrases for professional translators to go over, if necessary, and so we weren't dependent on the uptime of Google's servers for the functionality of our application.  (Not that Google has lousy uptime; but if by chance they ever take down the service or start charging for translations, we can't have our translations just &lt;em&gt;turn off&lt;/em&gt;).&lt;/p&gt;

&lt;h3&gt;The Actual Translation&lt;/h3&gt;
&lt;p&gt;This part was the easiest.  We just modify Globalize's ".t" method to use Google's translation service:&lt;/p&gt;

&lt;pre&gt;&lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;String&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;self.included&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;base&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:alias_method_chain&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:translate&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:google&lt;/span&gt;
    &lt;span class="ident"&gt;base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:alias_method&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:t&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:translate&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;translate_with_google&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;default&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;nil&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;arg&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;nil&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;local_base_language&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#CC7833"&gt;defined?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#DA4939"&gt;BASE_LANGUAGE&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span style="color:#DA4939"&gt;BASE_LANGUAGE&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;en&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

    &lt;span style="color:#BC9458"&gt;#don't translate this if it's already written in the target language&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span style="color:#CC7833"&gt;if&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Locale&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;language&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;iso_639_1&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="ident"&gt;local_base_language&lt;/span&gt;

    &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Locale&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;translate&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#DA4939"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;__translate__&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="ident"&gt;arg&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span style="color:#CC7833"&gt;unless&lt;/span&gt; &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt;  &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;__translate__&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt; 

    &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span class="punct"&gt;%Q{&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&amp;lt;span id=&amp;quot;translation_&lt;span class="expr"&gt;#{self.object_id}&lt;/span&gt;&amp;quot;&amp;gt;&lt;span class="expr"&gt;#{self}&lt;/span&gt;&amp;lt;/span&amp;gt;
                &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; 
                ......&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;The only flaw is that you can't use this on the labels of buttons or in javascript alert()s.  Instead of showing a translated string, it would display a huge mess of javascript.  I don't think there's a simple workaround for this, though, since the ".t" method can't know what context it is being called in.  So in your views, make sure all of your translated buttons use something like &lt;/p&gt;
&lt;pre&gt;&lt;span class="punct"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ident"&gt;input&lt;/span&gt; &lt;span class="ident"&gt;type&lt;/span&gt;&lt;span class="punct"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;submit&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="ident"&gt;value&lt;/span&gt;&lt;span class="punct"&gt;=&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&amp;lt;%= &lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Submit&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;.translate_without_google %&amp;gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;The Caching&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;&lt;em&gt;This&lt;/em&gt;&lt;/b&gt; part nearly killed me.  How do you cache the result of a google translation?  It never goes through our server!  The solution was a little convoluted, but &lt;em&gt;very&lt;/em&gt; educational to a guy who had never written a plugin before.&lt;/p&gt;

&lt;p&gt;First, we need to make the Javascript report the result of each translation back to our server.  Fortunately, Google's "translate" function offers a callback once the translation is complete.  So I just told it to execute the following:&lt;/p&gt;
&lt;pre&gt;&lt;span class="ident"&gt;new&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Ajax&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;Request&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;/cache_google_translation&lt;/span&gt;&lt;span class="punct"&gt;',{&lt;/span&gt;&lt;span class="ident"&gt;method&lt;/span&gt;&lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="ident"&gt;parameters&lt;/span&gt;&lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;phrase=&lt;span class="expr"&gt;#{self}&lt;/span&gt;&amp;amp;translation=&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;+&lt;/span&gt;&lt;span class="ident"&gt;result&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;translation&lt;/span&gt;&lt;span class="punct"&gt;});&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Next, we need a way for our Rails app to recognize the request for caching.  But how can a plugin respond to a request like a controller does?  It takes two steps. First you need to make a pseudo-controller that will do the caching:&lt;/p&gt;
&lt;pre&gt;&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;TricksController&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;ActionController&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;cache_google_translation&lt;/span&gt;
    &lt;span class="ident"&gt;bound_vars&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:translation&lt;/span&gt;&lt;span class="punct"&gt;],&lt;/span&gt; &lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:phrase&lt;/span&gt;&lt;span class="punct"&gt;]]&lt;/span&gt;
    &lt;span style="color:#DA4939"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;connection&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;execute&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;UPDATE globalize_translations SET built_in = 2, text = ? WHERE tr_key = ? AND language_id = &lt;span class="expr"&gt;#{Locale.language.id}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;.&lt;/span&gt;&lt;span class="ident"&gt;gsub&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;?&lt;/span&gt;&lt;span class="punct"&gt;'){&lt;/span&gt;&lt;span style="color:#DA4939"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;connection&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;quote&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;bound_vars&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;shift&lt;/span&gt;&lt;span class="punct"&gt;)})&lt;/span&gt;
    &lt;span style="color:#DA4939"&gt;Locale&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;translator&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;put_in_cache&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:phrase&lt;/span&gt;&lt;span class="punct"&gt;],&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Locale&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;language&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;iso_639_1&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;&lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:translation&lt;/span&gt;&lt;span class="punct"&gt;])&lt;/span&gt;
    &lt;span class="ident"&gt;render&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:text&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;And then you need to extend Rails' route parser to attach a URL to your controller.  (&lt;code&gt;alias_method_chain&lt;/code&gt; to the rescue!)&lt;/p&gt;
&lt;pre&gt;&lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;MapperExtensions&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;self.included&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;base&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;base&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:alias_method_chain&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:initialize&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:google_caching&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize_with_google_caching&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#BC9458"&gt;#we have to add ours FIRST, otherwise the final line of the regular routes.rb is usually a catchall that would intercept OUR route&lt;/span&gt;
    &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;add_route&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;/cache_google_translation&lt;/span&gt;&lt;span class="punct"&gt;',{&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:controller&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;google/tricks&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:action&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;cache_google_translation&lt;/span&gt;&lt;span class="punct"&gt;'})&lt;/span&gt;
    &lt;span class="ident"&gt;initialize_without_google_caching&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Finally, in your plugin's &lt;code&gt;init&lt;/code&gt; file you just attach these classes into Rails:&lt;/p&gt;
&lt;pre&gt;&lt;span style="color:#DA4939"&gt;ActionController&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Routing&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;RouteSet&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Mapper&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:include&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Google&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;MapperExtensions&lt;/span&gt;
&lt;span style="color:#DA4939"&gt;ActionView&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Helpers&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;AssetTagHelper&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:include&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Google&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Javascript&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;And that's it!  Well, not quite.  Did you notice the reference to Locale.translator.put_in_cache?  If you want to make sure that the auto-translations in your database are easily distinguishable so that you can have them manually translated later (machine translation isn't &lt;i&gt;quite&lt;/i&gt; there yet!) then you have to add an extra step.  It was easy enough to use a manual update statement instead of Locale.set_translation, which allowed me to set "built_in = 2" (that's how you recognize the auto-translations).  But then the 800 lb. gorilla gets in the way. Globalize maintains a separate cache of translations in memory to avoid wear and tear on the database, but if you don't update the copy in memory as well, Globalize will never actually USE your cached version!  It's a protected variable, so one more module extension:&lt;/p&gt;
&lt;pre&gt;&lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;LocalizeCacheAccess&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;put_in_cache&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;key&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;&lt;span class="ident"&gt;language&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;&lt;span class="ident"&gt;translation&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#D0D0FF"&gt;@cache&lt;/span&gt;&lt;span class="punct"&gt;[&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&lt;span class="expr"&gt;#{key}&lt;/span&gt;:&lt;span class="expr"&gt;#{language}&lt;/span&gt;:1&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;]&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;translation&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;and then include it in your app with&lt;/p&gt;
&lt;pre&gt;&lt;span style="color:#DA4939"&gt;Globalize&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;DbViewTranslator&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;send&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:include&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Google&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;LocalizeCacheAccess&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;And that's it!  Now you're REALLY done!  To get all of this code in a simple Rails plugin, download &lt;a href="http://www2.killswitchcollective.com/articles/globalize_with_google.zip"&gt;globalize_with_google.zip&lt;/a&gt; and unpack it in &lt;code&gt;#{RAILS_ROOT}/vendor/plugins/&lt;/code&gt;.&lt;/p&gt;</description>
      <pubDate>Fri, 13 Jun 2008 14:50:13 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/23</link>
      <guid>http://www.killswitchcollective.com/articles/23</guid>
    </item>
    <item>
      <title>Tips and Tricks for using the Globalize Plugin for Rails</title>
      <category>Development</category>
      <description>&lt;p&gt;Installing and configuring the Globalize plugin can be difficult, but it usually goes easily if you &lt;a href="http://www.artweb-design.de/2006/11/10/get-on-rails-with-globalize-comprehensive-writeup"&gt;follow the instructions&lt;/a&gt;.  There's nothing I can say about &lt;em style="font-style: italic"&gt;that&lt;/em&gt; that's not thoroughly covered elsewhere. Adapting your coding style to use it is &lt;a href="http://globalize-rails.org/wikipages/frequently-asked-questions"&gt;almost advertised&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But once you have it up and running there are a lot of subtle things that can still give you headaches down the line. For many of our international sites, the workflow is this: &lt;/p&gt;

&lt;ul&gt;&lt;li&gt;The client goes back and forth on designs, and we make a prototype like usual. &lt;/li&gt;&lt;li&gt;Before getting started, we installed the Globalize plugin. Once we have the site roughed out and the design is approved, Globalize has automatically generated a list of the unique phrases in our app. (You might want to clear the translations table and re-test every page so your list doesn't include obsolete or phased out phrases).&lt;/li&gt;&lt;li&gt;Then we hand the list to our client, who sends it to the team of translators, who send it back to us, and we slurp it into the database with a simple script (just loop through the languages and &lt;a href="http://globalize.rubyforge.org/classes/Globalize/Locale.html#M000024"&gt;Locale.set&lt;/a&gt;, then &lt;a href="http://globalize.rubyforge.org/classes/Globalize/Locale.html#M000034"&gt;Locale.set_translation&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;That's &lt;em style="font-style: italic"&gt;technically&lt;/em&gt; all you need to do to globalize your app. But, if you want your application to be as scalable as possible&#8212;or you'd like to keep your developers from pulling their hair out&#8212;here are some tips.&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;If at all possible, never translate phrases over 256 characters. If you do, you'll have to change globalize_translations.tr_key and and globalize_translations.text to a "text" field instead of a "string" (or varchar). That will work just fine, &lt;b&gt;but&lt;/b&gt;, if you can avoid having to do that, it will really pay off to be able to add an index to tr_key and language_id.&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;Make sure your validation messages are each translated individually, not en-masse in your view, or your translators will have extra work. That is, you want to avoid adding this to your translations table: "Errors: Name cannot be blank. Email address is not valid.", and instead have several seperate rows like this: "Errors:", "Name cannot be blank", "Email address is not valid" and so forth.&lt;/li&gt;&lt;li&gt;You may have to turn off view caching in your environments/production.rb if you're running a mongrel cluster, or you might get spontaneous, random language changes as your users navigate the site! (change config.action_controller.perform_caching and config.cache_classes to "false")&lt;/li&gt;&lt;li&gt;Don't stress too much about translating the word "Browse..." or "Choose File" on your file upload buttons- you can't change that! That's controlled by the user's browser, and it probably looks fine to them already.&lt;/li&gt;&lt;li&gt;Lastly, here's a handy query for generating statistics on how many translations you've added to the system for each language:&lt;/li&gt;&lt;/ul&gt;

&lt;pre style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;
SELECT english_name, COUNT(*) num_phrases 
FROM globalize_languages gl LEFT JOIN globalize_translations gt ON gl.id = gt.language_id
GROUP BY english_name ORDER BY english_name
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;And this rake task will generate a spreadsheet-checklist of phrases that still require translation.
&lt;br /&gt;&lt;a href="/article/translation_checklist.rake"&gt;translation_checklist.rake&lt;/a&gt;&lt;br /&gt;
Copy it to your lib/tasks folder, and run it to, in this case, make a pipe-delimited checklist, without a column for "English".&lt;/li&gt;&lt;/ul&gt;

&lt;pre style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;
rake skip_languages=English delimiter="|" db:translation_checklist &gt; whatever_file.csv
&lt;/pre&gt;
</description>
      <pubDate>Thu, 24 Jan 2008 17:46:10 -0800</pubDate>
      <link>http://www.killswitchcollective.com/articles/9</link>
      <guid>http://www.killswitchcollective.com/articles/9</guid>
    </item>
  </channel>
</rss>
