I'm a big fan of rspec and of rails' I18n, and I don't like having to study yml translation files over and
over to make sure every key has a translation in every language; so I wrote
it_should_have_no_missing_translations to test my templates for missing
translations.
Previously, I needed this for every template:
  it "should have no missing translations in fr" do
    I18n.locale = "fr"
    do_render
    response.should_not have_tag("span.translation_missing")
  end
  it "should have no missing translations in en" do
    I18n.locale = "en"
    do_render
    response.should_not have_tag("span.translation_missing")
  end
Where do_render knows how to render the template I'm testing.
If you're like me, and I presume you are, you're thinking the duplication up there is a
bit annoying and someone should do something about it. Well, here you go:
  it_should_have_no_missing_translations
You like? Obviously, it_should_have_no_missing_translations needs a bit of context, like
an implementation of do_render, and any other setup you need. Here's the implementation,
under the WTFPL. Copy it into your specs or make a helper out
of it that you include in your spec, or publish it in a gem and become famous.
  def it_should_have_no_missing_translations
    INSTALLED_LANGUAGES.each do |lang|
      it "should not have translations missing in #{lang}" do
        I18n.locale = lang
        do_render
        response.should_not have_missing_translations
      end
    end
  end
  have_missing_translations is defined thus:
  INSTALLED_LANGUAGES = [:en, :fr] unless defined?(INSTALLED_LANGUAGES)
  class TranslationsMissing
    def initialize(scope)
      @scope = scope
    end
    def matches? response
      if response.is_a? String
        root_node = HTML::Document.new(response, false, false).root
      else
        root_node = HTML::Document.new(response.body, false, false).root
      end
      m = @scope.css_select root_node, ".translation_missing"
      @missing_translations = []
      m.each do |mt|
        @missing_translations << mt.children.first
      end
      m.size > 0
    end
    def failure_message
      "expected that response would contain a translation_missing element, but it didn't"
    end
    def negative_failure_message
      "expected that response would contain no missing translations, but it contained these \n#{@missing_translations.join("\n")}"
    end
  end
  def have_missing_translations
    TranslationsMissing.new self
  end