The Wordchuck Blog. RSS

Shelly Shelly Roche is a Ruby on Rails engineer and founder of Wordchuck. She usually blogs about Ruby, Rails 3, woodchucks, i18n, l10n, startup life or whatever has her fired up on any given day. Yes, she is available for i18n consulting & training. Find her @shellyroche or by email.

» Wordchuck on TechCrunch TV

Wordchuck was just featured on TechCrunch TV!

Here's the video of my interview with the lovely Alexia Tsotsis:

» New Feature: Custom Locale Details

A new option has been added to the Project Settings page, allowing you to choose whether or not you'd like to use Wordchuck's library of locale details. Some of you have custom formats for your locales, so this setting will allow you to continue to use your own custom locale formats.

To change your setting, go to the "Settings" page for your project and select the option you'd like under the "Libraries & Dictionaries" heading: New Wordchuck Feature to include locale details

» Video: Internationalization (i18n) Tips and Tricks

In January, I gave a Tech Talk at Pivotal Labs on various aspects of Internationalization (i18n) and Localization (l10n) for web apps. My talk covered the basics of i18n, common pitfalls, hidden challenges, and some of the things I've learned building Wordchuck.

Here's the video:

Part 1:

Part 2:

Part 3:

Check out some of Pivotal's other awesome Tech Talks - they cover a wide variety of topics and services helpful for engineers and startups.

» New Wordchuck Features Released

Over the last month, we've been busy gathering feedback from our awesome beta users (and fixing a few bugs). As a result, we're excited to announce the launch of several new features:

"Translation Summary" page

  • See the status of your translations for each language
  • See at a glance which languages still need human translation
  • Submit all remaining machine translations for a language to professionals
Wordchuck's new translation summary page

"Translation Details" page

  • Lists all translations for a language, for each status (for example, review all of the translations completed by your invited translators.)
  • Click to make changes.
Wordchuck's new translation detail page

The Wordchuck API

  • Our new API allows anyone to use Wordchuck to internationalize their website, mobile app, or software.
  • Documentation is underway, but hasn't been posted yet. In the meantime, please let us know if you need instructions, and we'll get you everything you need.
  • We'll make a separate announcement with details and documentation as soon as we're ready.

Library of Common Terms

  • We've launched our Library of professionally-translated words and phrases that are commonly used on websites and apps -- things like buttons, menu items, labels, etc. We're getting these items professionally translated and making them available to you for free!
  • It's all automatic - when you create or update content, we'll check to see if the translations already exist in our library and instantly populate those that do. This feature should save everyone a bunch of money, AND improve the translation quality of your site (especially if you're starting off with only machine translations).
  • We're constantly adding to the library, so please let us know if you'd like something added.

Library of Localized Formats

  • Similar in concept to our Library of Common Terms, our new Library of locale-specific formats includes things like dates, times, currencies and more. The library is human-translated, and exists for 28 languages so far. Details are posted here.
  • And you know the drill: if there's a locale you'd like added to the library, just let us know.

Performance Improvements & Bug Fixes

  • We're running faster and better than ever. Of course, in true startup form, there are still a few things in the queue to be fixed.
  • If you run into anything, let us know and we'll shift priorities to get on it.

Big, HUGE Thanks

We really, really appreciate all of your feedback, bug reports, patience, encouragement and support. We love all of your feedback, but it especially makes our day when we hear we've been able to save someone a boatload of time and money. That's why we're doing this!

So keep the feedback coming. Our #1 goal is to keep you happy, and to keep saving you time and money in new ways. Drop us a line anytime and let us know how we can make your experience even better.

» Recurly Goes International with Wordchuck

Subscription billing provider Recurly has launched the internationalized version of their service, localized to 10 languages: English, French, Spanish, German, Dutch, Hindi, Chinese, Japanese, Russian, and Portuguese.

According to founder Isaac Hall, the company is already seeing a payoff:

Using Google Analytics, we noticed the average English speaker is able to complete the hosted payment page in under 2 minutes. Other languages averaged 5-10 minutes and some Asian languages took even longer. Already, the time required to complete the payment page is dropping quickly.

On their experience using Wordchuck to simplify the internationalization process:

If you’re a ruby developer looking to internationalize your own application, I highly recommend taking a look at Wordchuck. Wordchuck helped us submit all our language strings to professional translators for our supported languages. If you’re on a budget, you can get started quickly by using their automatic Google Translate submissions.

HUGE thanks to Isaac and Recurly for giving us invaluable feedback throughout the process, and for helping us make Wordchuck even better.

Read more about Recurly's internationalization process at their blog.

Stay tuned to the Wordchuck blog for announcements on some of our Recurly-inspired enhancements, and some exciting new features launching very soon!

» Wordchuck Launches Library of Locale-Specific Details

We've just launched our Library of Locale-Specific Details, which includes professionally translated localized information for 28 locales!

The library is free for Wordchuck projects, and means you automatically get locale-specific data (like date formats, currencies, date/times, numbers, etc.) included when you generate your locale files using our Ruby gem.

The Library includes locale details for:

» Arabic
» British (en-GB)
» Chinese (zh-CN)
» Croatian
» Czech
» Danish
» Dutch
» English
» Estonian
» Finnish
» French
» German
» Greek
» Hindi
» Indonesian
» Italian
» Japanese
» Latvian
» Polish
» Portuguese
» Romanian
» Russian
» Slovak
» Spanish
» Spanish - Latin America (es-AR)
» Swedish
» Turkish
» Vietnamese

To include the new locale data, simply run:
rake wordchuck:generate
This will re-create your locale files. When the rake task completes, you'll see the new locale-specific formats at the top of your locale files.

We're working on including more, so please let us know if you have specific locales you'd like added.

» I18n Tech Talk at Pivotal Labs

Thanks to Pivotal Labs for inviting me to give a Tech Talk on Internationalization and Localization (try saying that fast, ten times...). My slides are posted below, video to follow as soon as it's posted at Pivotal:

Thanks to all who attended, particularly those who did not throw fruit.

» How to: Localize in Rails 3

What is Localization?

If you want to offer your site in more than one language, you need to go through a process called internationalization (i18n) and localization (l10n).

As I wrote about in my previous post, the internationalization process involves abstracting your site’s content strings, dates and currencies from your application and replacing each abstracted piece of content with a placeholder.

The localization process involves translating your content and providing other locale-specific information, like date and currency formats.

Localization in Rails 3:

In Rails 3, all dependencies for localization (l10n) are automatically included as part of the built-in i18n library, so let’s take a look at the three steps required to localize your site using i18n’s default Simple backend (more on that in another post):

Step 1: Add translations

When we internationalized our site, we created a locale file to store our site's content strings in our default language -- for this example, English. We created our base locale file in config/locales and called it en.rb. If we want to offer our site in French, we need to add a new locale file to config/locales (called fr.rb) to store our French translations. The French locale file will look very similar to our base English locale file, except:
»  the language code will be :fr instead of :en, and
»  the descriptive content keys (:page_title, for example) will point to the French translation:
  :fr =>
    :welcome => {
      :page_title => "Bienvenue sur mon site!"

We'll create a new locale file for each language, so if we are translating into French, Spanish and Japanese, we'll end up with four files in our config/locales directory: en.rb, fr.rb, es.rb and ja.rb.

As you can imagine, managing content and translations becomes pretty cumbersome as our site's content grows and changes. Every change requires us to generate new translations, then copy and paste the new translations into each locale file.

Step 2: Define locale-specific formats

Localization involves more than just translating a content string from one language into another. We also need to tell our app how to display locale-specific content like dates and currencies.
  :'fr' =>
    :date => {
      :abbr_day_names => ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
      :abbr_month_names => ["day", "month", "year"],
      :day_names => ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
      :month_names => ["~", "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
      :formats => {
        :default => "%d/%m/%Y",
        :long => "%e %B %Y",
        :long_ordinal => "%e %B %Y",
        :only_day => "%e",
        :short => "%e %b"
We can find community-contributed locale-specific formats for a multitude of locales on github. These files are a great starting point, but it's definitely a good idea to review each one for consistency and errors before using it in a production environment.

Step 3: Use localized formats

To use a localized date format, we simply specify the :format in our view code, like so:
<div id="content">
  <h2>Last updated <%= post.updated_at, :format => :short -%></h2>
YAY - we now have a properly internationalized and localized Rails 3 app, and are ready to look at ways to add some sophistication. Next up: Activerecord validation error messages.

Additional Resources:

If you'd like to play around with a fully-featured Rails 3 i18n and l10n implementation, check out our demo app here. The source code is available on github, so you can see exactly how everything works.

The official Rails i18n API documentation is excellent, and packed with all sorts of additional details and options for doing more complex implementations of internationalization and localization.

» How to: Internationalize in Rails 3

What is Internationalization?

If you want to offer your site in more than one language, you need to go through a process called internationalization (i18n) and localization (l10n).

The internationalization process involves abstracting your site’s content strings, dates and currencies from your application and replacing each abstracted piece of content with a placeholder.

The localization process involves translating your content and providing other locale-specific information, like date and currency formats.

Internationalization in Rails 3:

In Rails 3, all dependencies for Internationalization (i18n) are already included, so let’s take a look at the three steps required to get up and running using I18n’s default Simple backend (more on that in another post):

Step 1: Abstract your content

Each piece of text we want to translate will need to be copied into a new file, called a locale file. Locale files can be .yml or .rb format, and will be created in our config/locales directory. Our base locale is the default language for our site, in my case, English. My base locale file is therefore called en.rb (config/locales/en.rb).

In this example, I’m copying the “Welcome to my site!” string from my view:
<div id="content">
  <h2>Welcome to my site!</h2>
  <p>You are going to like it here.</p>
...into my en.rb locale file, and scoping it within my “welcome” section, with a descriptive key called “page_title”:
  :en =>
    :welcome => {
      :page_title => "Welcome to my site!"
We would repeat that process for every piece of content on the site.

Step 2: Insert placeholders

As we abstract each piece of content, we need to replace it with a placeholder that will perform the lookup to display the properly translated content to our users. Continuing with the example from above, we're going to replace the “Welcome to my site!” text in our view with our call to I18n’s translate method, passing in the section and key we defined in our locale file to help identify the correct piece of content:
<div id="content">
  <h2><%= t(:page_title, :scope => :welcome) -%></h2>
  <p>You are going to like it here.</p>

Step 3: Set the locale

The final step is to set up your application to pass the locale, so the i18n placeholders know which translations to display.

The default locale is updated on every request within a before_filter set in the ApplicationController:
before_filter :set_locale

def set_locale
  I18n.locale = params[:locale] if params.include?('locale')

The idea here is to set the locale on every route automatically, based on what locale is set for the current request:
def default_url_options(options = {})
  options.merge!({ :locale => I18n.locale })

In order for this to work, we need to wrap every applicable route within a :locale scope to ensure that the locale is included within any named routes.

Your routes file will look something like this:
scope ':locale' do
  resources :buckets, :only => [ :index ]
  resources :items, :only => [ :index, :show, :edit, :update ]

This way, we don’t have to specify the locale manually when we call a named route method. It all happens automatically for us.

Of course, you do not have to store the locale in the URL if you do want to. It can also be stored in browser's session[], or as a data item within the user's database profile. But placing the desired locale in the URL ensures that bookmarks or shared links always point to the content as the user was looking at it.

YAY - we've made it through a simple i18n setup and ready for our next step: localization!

Additional Resources:

If you'd like to play around with a fully-featured Rails 3 i18n implementation, check out our demo app here. The source code is available on github, so you can see exactly how everything works.

The official Rails i18n API documentation is excellent, and packed with all sorts of additional details and options for doing more complex implementations of internationalization.

» Running on Async Rails 3

Wordchuck is now running on async Rails!

It was actually quite simple to set up, as I was already running Rails 3 + Ruby 1.9.2. I used @ejwcom's Async Rails 3.x demo project on github as a reference, as well as some, er, light reading from @igrigorik, and everything pretty much worked as advertised.

Step 1: Use the right gems

Add these gems to your Gemfile:
  source ''
  gem 'rails', '3.0.0'
  gem 'rake', '0.8.7'
  gem 'thin', '1.2.7'
  gem 'rack', '>=1.0.0'

  gem 'eventmachine', '0.12.10'
  gem 'rack-fiber_pool', :require => 'rack/fiber_pool'
  gem 'mysql2', '0.2.4'
  gem 'builder'

Step 2: Enable threaded mode

In your various environments' configs:
  # Enable threaded mode

Step 3: Add FiberPool as Rack middleware

Update to use FiberPool as Rack middleware. This will make sure each request runs in its own fiber:
use Rack::FiberPool
run Wordchuck::Application

Step 4: Configure MySQL for async use

Set the adapter in database.yml to "em_mysql2" to run mysql in async mode with ActiveRecord and Rails:
  adapter: em_mysql2

Step 5: Fix your rake tasks

When I enabled threaded mode in my production environment...
  # Enable threaded mode
...I broke the rake tasks and delayed_job startup script I use in production. To fix this, I can create a new environment called "tasks", that has the same config as my old production.rb file, in particular, turning thread safety off:
  # Enable threaded mode
  # config.threadsafe!
Then, I can run my rake tasks & script using RAILS_ENV=tasks instead of RAILS_ENV=production.

Step 6: Restart your app

At this point, restart your app, and you should be running async Rails!

One thing to note is that many gems aren't yet fiber-aware, so they will be blocking. You can still use them, just be aware that they'll prevent you from seeing the full performance benefit of running asynchronously.

To find out more about how this all works, watch the video at the end of Ilya's post, and read this article from @mperham.

» Wordchuck at the TechCrunch Disrupt Hackathon

I hit the TechCrunch Disrupt Hackathon this weekend to do a little hacking with 450 or so of my nerdiest friends. All in all, an awesome experience -- even the part where I had to get on stage and give a 60 second demo of my hack.

Alexia from TechCrunch interviewed me after I'd had a few too many red bulls... Here's her video of us chatting about the hackathon, Wordchuck, and salad bugs:

Read the rest of her article: An Illustrated Slice of TC Disrupt Hackathon Life

Other highlights:
* The midnight taco truck (thanks, BitTorrent!)
* Finding a spot to park the RV at 3:00 in the morning so I could take a quick nap before demo time
* Meeting some supercool fellow hackers, journalists and people I follow on Twitter
* And of course, Andy Brett's awesome shirt:

Hands down, @andrewpbrett wins for best shirt at #hackdisrupt  on Twitpic

And with that, back to coding! The 'Chuck has entered final launch sequence... stay tuned :)

» The Wordchuck Wagon

The Wordchuck wagon (my sweet 1993 24' Class C motorhome/office) is currently en route from Vancouver, BC, to San Francisco. Here we are, somewhere in Oregon (the wordchuck is not pictured):

» How to use check_box in nested model forms

This article will show you how to use the check_box helper in a nested model form using Rails 3.

In this example, I have two models with a many-to-many relationship - projects and languages

Because a project can have many languages and a language can have many projects, I'm going to use a :has_many association with the :through option and create a join model called project_languages to store the relationships:

class Language < ActiveRecord::Base  
  has_many :project_languages
  has_many :projects, :through => :project_languages
class Project < ActiveRecord::Base
  has_many   :project_languages
  has_many   :languages, :through => :project_languages

The join model:

class ProjectLanguage < ActiveRecord::Base
  belongs_to :project
  belongs_to :language

We need to do one more thing to set our models up properly - tell the project model to automatically include the project_languages model when it saves. To do this, we add accepts_nested_attributes_for to the model like so:

class Project < ActiveRecord::Base
  has_many   :project_languages
  has_many   :languages, :through => :project_languages

  accepts_nested_attributes_for :project_languages, :allow_destroy => true

Using accepts_nested_attributes_for means we don't need to add anything special in the projects controller - it will automatically save the project_languages object when we save the project object.

In the edit form for my project, I want to list all of the languages that have been associated with the project, and then allow the user to check the box next to each language to set a flag in my nested project_languages model.

To give you more context for this functionality, part of the OpenGab awesomeness is that we have a rake task that automatically generates and deploys all of your translated .yml files. The flag we're setting with each check box indicates that all of the content for that language has been translated and is ready to be deployed to the production environment for the project. Languages that aren't checked remain associated with the project (i.e. the association in the project_languages model is not destroyed), but they will be ignored when the rake task to re-generate the yml files runs for the production environment.

The next step is to create the form in my project edit form view (this example is using Rails 3):

=form_for @project, :remote => true do |form|  

Then within the form:

-for language in @project.project_languages
  =form.fields_for :project_languages, language do |pl|
    =pl.check_box :production

For each language that has been added to the project (through the nested project_languages join model), we're printing a check_box that will update an attribute in the project_languages model called "production".

This is what it looks like in the form:

This is the html that is generated:

<input name="project[project_languages_attributes][0][production]" type="hidden" value="0" /><input id="project_project_languages_attributes_0_production" name="project[project_languages_attributes][0][production]" type="checkbox" value="1" />
<label for="project_project_languages_attributes_0_Spanish">Spanish</label>
<input id="project_project_languages_attributes_0_id" name="project[project_languages_attributes][0][id]" type="hidden" value="11" />
<input name="project[project_languages_attributes][1][production]" type="hidden" value="0" /><input checked="checked" id="project_project_languages_attributes_1_production" name="project[project_languages_attributes][1][production]" type="checkbox" value="1" />
<label for="project_project_languages_attributes_1_French">French</label>
<input id="project_project_languages_attributes_1_id" name="project[project_languages_attributes][1][id]" type="hidden" value="12" />
<input name="project[project_languages_attributes][2][production]" type="hidden" value="0" /><input id="project_project_languages_attributes_2_production" name="project[project_languages_attributes][2][production]" type="checkbox" value="1" />
<label for="project_project_languages_attributes_2_Estonian">Estonian</label>
<input id="project_project_languages_attributes_2_id" name="project[project_languages_attributes][2][id]" type="hidden" value="8" />

And that's it!