7

I have an application (my_test_app) with working i18n support built. Currently, there are two language files available, FR & EN, and if I toggle back and forth between them, everything works as I expect to see it for non-engine functions such as the User index/show/edit/delete (ISED) options.

Within my_test_app I have a Rails Engine mounted (my_engine) which has a controller & model set (engine_job). So, a workable URL should be

http://0.0.0.0:3000/fr/my_engine/engine_job

No matter what language I choose, however, it always shows up in EN. Examining the parameters shows:

--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess
locale: fr
action: index
controller: my_engine/engine_job

And yet the chosen translation is EN.

my_test_app route.rb:

MyTestApp::Application.routes.draw do

  scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
    mount MyEngine::Engine, at: "/my_engine"
  end # scope locale

  match '*path', to: redirect("/#{I18n.default_locale}/%{path}"), constraints: lambda { |req| !req.path.starts_with? "/#{I18n.default_locale}/" and !req.path == "/#{I18n.default_locale}/"}
  match '', to: redirect("/#{I18n.default_locale}/")
end

my_engine route.rb:

MyEngine::Engine.routes.draw do
  resources :my_jobs
end

rake routes:

my_engine        (/:locale)/my_engine           MyEngine::Engine {:locale=>/en|fr/}
                             /*path(.:format)                    :controller#:action
                             /                                   :controller#:action
                users GET    (/:locale)/users(.:format)          users#index {:locale=>/en|fr/}
                      POST   (/:locale)/users(.:format)          users#create {:locale=>/en|fr/}
             new_user GET    (/:locale)/users/new(.:format)      users#new {:locale=>/en|fr/}
            edit_user GET    (/:locale)/users/:id/edit(.:format) users#edit {:locale=>/en|fr/}
                 user GET    (/:locale)/users/:id(.:format)      users#show {:locale=>/en|fr/}
                      PUT    (/:locale)/users/:id(.:format)      users#update {:locale=>/en|fr/}
                      DELETE (/:locale)/users/:id(.:format)      users#destroy {:locale=>/en|fr/}
             sessions POST   (/:locale)/sessions(.:format)       sessions#create {:locale=>/en|fr/}
          new_session GET    (/:locale)/sessions/new(.:format)   sessions#new {:locale=>/en|fr/}
              session DELETE (/:locale)/sessions/:id(.:format)   sessions#destroy {:locale=>/en|fr/}
               signup        (/:locale)/signup(.:format)         users#new {:locale=>/en|fr/}
               signin        (/:locale)/signin(.:format)         sessions#new {:locale=>/en|fr/}
              signout DELETE (/:locale)/signout(.:format)        sessions#destroy {:locale=>/en|fr/}
                 help        (/:locale)/help(.:format)           static_pages#help {:locale=>/en|fr/}
                about        (/:locale)/about(.:format)          static_pages#about {:locale=>/en|fr/}
                 root        /(:locale)(.:format)                static_pages#home {:locale=>/en|fr/}

Routes for MyEngine::Engine {:locale=>/en|fr/}:
    engine_jobs GET    /engine_jobs(.:format)          my_engine/engine_jobs#index
                POST   /engine_jobs(.:format)          my_engine/engine_jobs#create
 new_engine_job GET    /engine_jobs/new(.:format)      my_engine/engine_jobs#new
edit_engine_job GET    /engine_jobs/:id/edit(.:format) my_engine/engine_jobs#edit
     engine_job GET    /engine_jobs/:id(.:format)      my_engine/engine_jobs#show
                PUT    /engine_jobs/:id(.:format)      my_engine/engine_jobs#update
                DELETE /engine_jobs/:id(.:format)      my_engine/engine_jobs#destroy

Further, any links clicked within the Engine set the language to EN. A few web searches shed no real light as to what might be happening here, since all i18n examples I could find do not involve mounted engines.

EDIT: show code to set default locale my_test_app/app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  protect_from_forgery
  helper MyEngine::Engine.helpers

  ActiveRecord::Base.verify_active_connections!

  before_filter :set_locale

  private
    def set_locale
      I18n.locale = params[:locale] || I18n.default_locale
      Rails.application.routes.default_url_options[:locale]= I18n.locale
      logger.debug "My_Test_App:  default_url_options is passed options: #{Rails.application.routes.default_url_options.inspect}\n"
      # current_user.locale
      # request.subdomain
      # request.env["HTTP_ACCEPT_LANGUAGE"]
      # request.remote_ip
    end # set_locale

end # class ApplicationController

/EDIT

EDIT2 (Route Map after changes suggested by Pierre Aug 9 at 15:59):

An application route yeilds http://0.0.0.0:3000/en/users with "locale"=>"en". A Helper created engine route looks like http://0.0.0.0:3000/my_engine?locale=en/engine_jobs and yeilds No route matches [GET] "/my_engine".

my_engine_plugin        /my_engine                     MyEnginePlugin::Engine
                users GET    (/:locale)/users(.:format)          users#index {:locale=>/en|fr/}
                      POST   (/:locale)/users(.:format)          users#create {:locale=>/en|fr/}
             new_user GET    (/:locale)/users/new(.:format)      users#new {:locale=>/en|fr/}
            edit_user GET    (/:locale)/users/:id/edit(.:format) users#edit {:locale=>/en|fr/}
                 user GET    (/:locale)/users/:id(.:format)      users#show {:locale=>/en|fr/}
                      PUT    (/:locale)/users/:id(.:format)      users#update {:locale=>/en|fr/}
                      DELETE (/:locale)/users/:id(.:format)      users#destroy {:locale=>/en|fr/}
             sessions POST   (/:locale)/sessions(.:format)       sessions#create {:locale=>/en|fr/}
          new_session GET    (/:locale)/sessions/new(.:format)   sessions#new {:locale=>/en|fr/}
              session DELETE (/:locale)/sessions/:id(.:format)   sessions#destroy {:locale=>/en|fr/}
               signup        (/:locale)/signup(.:format)         users#new {:locale=>/en|fr/}
               signin        (/:locale)/signin(.:format)         sessions#new {:locale=>/en|fr/}
              signout DELETE (/:locale)/signout(.:format)        sessions#destroy {:locale=>/en|fr/}
                 help        (/:locale)/help(.:format)           static_pages#help {:locale=>/en|fr/}
                about        (/:locale)/about(.:format)          static_pages#about {:locale=>/en|fr/}
                 root        /(:locale)(.:format)                static_pages#home {:locale=>/en|fr/}

Routes for MyEnginePlugin::Engine:
    engine_jobs GET    (/:locale)/engine_jobs(.:format)          my_engine_plugin/engine_jobs#index {:locale=>/en|fr/}
                     POST   (/:locale)/engine_jobs(.:format)          my_engine_plugin/engine_jobs#create {:locale=>/en|fr/}
 new_engine_job GET    (/:locale)/engine_jobs/new(.:format)      my_engine_plugin/engine_jobs#new {:locale=>/en|fr/}
edit_engine_job GET    (/:locale)/engine_jobs/:id/edit(.:format) my_engine_plugin/engine_jobs#edit {:locale=>/en|fr/}
     engine_job GET    (/:locale)/engine_jobs/:id(.:format)      my_engine_plugin/engine_jobs#show {:locale=>/en|fr/}
                     PUT    (/:locale)/engine_jobs/:id(.:format)      my_engine_plugin/engine_jobs#update {:locale=>/en|fr/}
                     DELETE (/:locale)/engine_jobs/:id(.:format)      my_engine_plugin/engine_jobs#destroy {:locale=>/en|fr/}

/EDIT2 (Route Map after changes suggested by Pierre Aug 9 at 15:59)

So, the question is what changes do I need to make to my routes or engine to get this to work as expected?

Thanks in advance for your time and suggestions!

MichelV69
  • 1,247
  • 6
  • 18

1 Answers1

4

I am using Engine with I18n and it works fine. I created a dummy Rails application to try your scenario. As far as I know, changing the locale inside the URL works fine with routes defined in your Rails application:

My routes:

Bar::Application.routes.draw do
  root 'posts#index'

  scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
    resources :posts, only: :index
  end
end

I am able to change the I18n locale with:

http://localhost:3000/fr/posts
http://localhost:3000/en/posts

I think that your probleme is when you want to go to any of your engine's routes since you did not set the I18n locale switch. See bellow:

engine_jobs GET    /engine_jobs(.:format)

Then, when going to /engine_jobs even if you specified a locale in the URL, it's I18n default locale that will be set (en):

def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
  # ...
end

When using your Engine routes, params[:locale] is nil

Solution

Add the same logic to your engine's routes:

config/routes.rb

MyTestApp::Application.routes.draw do

  mount MyEngine::Engine, at: "/my_engine"

  match '*path', to: redirect("/#{I18n.default_locale}/%{path}"), constraints: lambda { |req| !req.path.starts_with? "/#{I18n.default_locale}/" and !req.path == "/#{I18n.default_locale}/"}
  match '', to: redirect("/#{I18n.default_locale}/")
end

your_engine/config/routes.rb

MyEngine::Engine.routes.draw do
  scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
    resources :my_jobs
  end
end

mount MyEngine::Engine, at: "/my_engine" only tells rails to "load" all engine routes. If you need to add constraints, scopes, namespace or anything else, you should use usual rails way to do it but in your engine's routes file.

Finally you need to update both of your application_controller.rb (main app + engine) with the following:

class ApplicationController < ActionController::Base
  def url_options
    { locale: I18n.locale }
  end

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
    Rails.application.routes.default_url_options[:locale]= I18n.locale
  end
end
Pierre-Louis Gottfrois
  • 17,561
  • 8
  • 47
  • 71
  • Hello, Pierre-Louis. Still not working, however, we have changed the error condition: For a URL of: 0.0.0.0:3000/fr/my_engine/engine_job I get: ActionController::RoutingError (No route matches [GET] "/fr/my_engine/engine_job"): – MichelV69 Aug 12 '13 at 15:14
  • Can you update your question if the new routes ? Also, how do access the page, typing the URL directly or through UrlHelpers ? If so, don't forget to add `use_route :your_engine` – Pierre-Louis Gottfrois Aug 12 '13 at 16:16
  • Hi, Pierre, sorry, I didn't get the notification that you had answered. I'm going to award you the bounty since you've been so willing to help out, and we can continue sorting this out. I'll add more info this afternoon when I have a chance between customers. – MichelV69 Aug 15 '13 at 15:54
  • Great, I'll be happy to help you figure out what is wrong here. – Pierre-Louis Gottfrois Aug 15 '13 at 16:54
  • Pierre, I've added the updated route map. I am not using "use_route :your_engine" anywhere ... I couldn't see a good google example of how/ where it should be implemented, so if you could give an example, that would be a huge help. Thanks again. – MichelV69 Aug 16 '13 at 12:34
  • My bad, `use_route` are for controller specs. What I meant was when using `UrlHelpers` with engine: `MyEngine.engine_jobs_path`. Take a look here http://edgeguides.rubyonrails.org/engines.html – Pierre-Louis Gottfrois Aug 16 '13 at 13:17
  • Pierre: I'm using MyEngine.engine_jobs_path style UrlHelpers for all my links. Nothing is "by hand". – MichelV69 Aug 17 '13 at 11:55
  • Try to print the locale params on the `ApplicationController` when clicking on one of your engine path. The locale should be good whether in `fr` or in `en` – Pierre-Louis Gottfrois Aug 18 '13 at 08:48
  • I removed my last comment, because I discovered I had improperly implemented your solution. An application route yeilds `http://0.0.0.0:3000/en/users` with `"locale"=>"en"`. A Helper created engine route looks like `http://0.0.0.0:3000/my_engine?locale=en/engine_jobs` and yeilds `No route matches [GET] "/my_engine"`. – MichelV69 Aug 19 '13 at 15:28
  • 2
    Yes I think rails can't find the the routes since it wants the locale not as a params but inside the url... Try to add this code in your `set_locale` method in `application_controller.rb`: `Rails.application.routes.default_url_options[:locale] = I18n.locale`. Then define a `url_options ` method in the controller. See here for more details http://stackoverflow.com/questions/14875404/rails-mountable-engine-under-a-dynamic-scope#comment26855682_14875404 and here http://stackoverflow.com/questions/17207104/setting-default-url-options-for-mounted-rails-engine/18299975#18299975 – Pierre-Louis Gottfrois Aug 19 '13 at 15:37
  • Well that's a step in the right direction. `http://0.0.0.0:3000/en/my_engine/engine_jobs` is now the working URL, with `Parameters: {"locale"=>"en"}`. So that's good. However, it yields `No route matches` and the contents of locale seem to now include other parameter data. – MichelV69 Aug 19 '13 at 16:35
  • Ok. Making progress. The problem is the URL Helpers in the engine don't seem to be making valid URLs. So the error that is getting thrown is not the attempt to get to the engine content; that works. It is the "link_to" command in the engine_job/index.html.erb that is throwing the error. – MichelV69 Aug 19 '13 at 17:19
  • Adding the same code to the Engine application controller file has corrected the problem. Everything works as I expect it to now. Bravo, @Pierre-Louis Gottfrois ... thanks again for the excellent and persistent help. – MichelV69 Aug 19 '13 at 18:23
  • I'm glad to hear that ! I'll add the above element to my answer. Well done ;) – Pierre-Louis Gottfrois Aug 19 '13 at 19:23
  • Not working for me (or I don't understand the answer). I'm mounting my engine inside the optional locale scope, like the op. NOT directly in the root as implied in the answer. That would not give me the correct routes. I need locale/my_engine/engine_controller... not my_engine/locale/engine_controller. Or do I misunderstand something here? Advice? Thanks. – Nico Sep 23 '15 at 07:13
  • Fixed it using this solution: http://stackoverflow.com/questions/14875404/rails-mountable-engine-under-a-dynamic-scope – Nico Sep 23 '15 at 07:46