< Return to Blog

Graceful locale-based content fallback in RefineryCMS

This hack took me through the refinerycms-i18n gem, and I discovered that it does not aid in achieving graceful content fallback, amongst other issues.

It should be noted this bit of work focused on Refinery v2.0.9 and may not be entirely applicable with the current release.

My solution involved adding the following decorator,

puts "Overriding Refinery::Page - #{__FILE__[/(vendor[\/\w\.]+)/]}"

Refinery::Page.class_eval do

  # The `Refinery::Page.find_by_path_or_id` call in
  # `Refinery::PagesController#find_page` calls `Refinery::Page.find_by_path` if
  # `params[:path].friendly_id?` is true.
  #
  #   # With slugs scoped to the parent page we need to find a page by its full path.
  #   # For example with about/example we would need to find 'about' and then its child
  #   # called 'example' otherwise it may clash with another page called /example.
  #   def self.find_by_path(path)
  #     split_path = path.to_s.split('/').reject(&:blank?)
  #     page = ::Refinery::Page.by_slug(split_path.shift, :parent_id => nil).first
  #     page = page.children.by_slug(split_path.shift).first until page.nil? || split_path.empty?
  #
  #     page
  #   end
  #
  # It can be seen that `Refinery::Page.by_slug` is used to scope results via
  # the Globalize gem, only returning pages that have translations for the
  # locale.

  if defined?(::Refinery::I18n)
    raise ScriptError, "Please remove `refinerycms-i18n` from your Gemfile!"
  end

  # Finds pages by their slug.  See by_title
  def self.by_slug(slug, conditions={})
    #locales = Refinery.i18n_enabled? ? Refinery::I18n.frontend_locales.map(&:to_s) : ::I18n.locale.to_s
    results = with_globalize({ :locale => ::I18n.locale.to_s, :slug => slug }.merge(conditions))
    return results if results && results.any?

    # NOTE get results for the default locale
    with_globalize({ :locale => ::I18n.default_locale.to_s, :slug => slug }.merge(conditions))
  end
end

My hack is detailed rather clearly, and works on whichever approach is taken to obtain the locale context.

However, my work presented another hurdle — to ensure the locale is the first path segment. Getting this to work with refinery placed a few challenges which I was able to get around with

  get "/", to: redirect('/en/')
  scope "/:locale", constraints: ->(r){r.path !~ %r{^/refinery}} do
    mount Refinery::Core::Engine, :at => '/'
  end

  mount Refinery::Core::Engine, :at => '/' # for CMS access via /refinery

My friend David helped with the above in reminding me that Refinery needed to be mounted outside the scope as well, for CMS access.