Skip to content

Commit

Permalink
Support i18n in CC
Browse files Browse the repository at this point in the history
This patch includes the code to introduce i18n into CC, contains:
(1). i18n init code
(2). Change the base controller to accept new HTTP_ACCEPT_LANGUAGE param
     which identifies the locale of request client
(3). Enhanced ApiError class to translate the error message for REST API
     response.

This patch does not include concrete REST API controller changes to
enable i18n. The changes of concrete REST API controllers' changes
will be submitted in other patches.
  • Loading branch information
xingzhou committed Jul 25, 2014
1 parent e11a9d1 commit f171613
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 17 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ gem 'rake'
gem 'bcrypt-ruby'
gem 'eventmachine', '~> 1.0.0'
gem 'fog'
gem 'i18n'
gem 'nokogiri', '~> 1.6.2'
gem 'unf'
gem 'netaddr'
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ DEPENDENCIES
fakefs
fog
httpclient
i18n
loggregator_emitter (~> 3.0)
machinist (~> 1.0.6)
membrane (~> 1.0)
Expand Down
1 change: 1 addition & 0 deletions app/controllers/base/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def inject_dependencies(dependencies = {})
# body string], or just a body string.
def dispatch(op, *args)
logger.debug "cc.dispatch", endpoint: op, args: args
I18n.locale = env['HTTP_ACCEPT_LANGUAGE']
check_authentication(op)
send(op, *args)
rescue Sequel::ValidationFailed => e
Expand Down
3 changes: 3 additions & 0 deletions lib/cloud_controller/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ class Config < VCAP::Config
],

optional(:app_bits_upload_grace_period_in_seconds) => Integer,

optional(:default_locale) => String
}
end

Expand Down Expand Up @@ -235,6 +237,7 @@ def merge_defaults(config)
config[:billing_event_writing_enabled] = true if config[:billing_event_writing_enabled].nil?
config[:skip_cert_verify] = false if config[:skip_cert_verify].nil?
config[:app_bits_upload_grace_period_in_seconds] ||= 0
config[:default_locale] ||= "en_US"
sanitize(config)
end

Expand Down
10 changes: 10 additions & 0 deletions lib/cloud_controller/runner.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require "steno"
require "optparse"
require "i18n"
require "i18n/backend/fallbacks"
require "vcap/uaa_token_decoder"
require "vcap/uaa_verification_key"
require "cf_message_bus/message_bus"
Expand All @@ -26,10 +28,18 @@ def initialize(argv)
@config_file = File.expand_path("../../../config/cloud_controller.yml", __FILE__)
parse_options!
parse_config

init_i18n

@log_counter = Steno::Sink::Counter.new
end

def init_i18n
I18n.default_locale = @config[:default_locale]
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.load_path = Dir[File.expand_path("../../../vendor/errors/i18n/*.yml", __FILE__)]
end

def logger
@logger ||= Steno.logger("cc.runner")
end
Expand Down
7 changes: 6 additions & 1 deletion lib/vcap/errors/api_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ def message
formatted_args = args.map do |arg|
(arg.is_a? Array) ? arg.map(&:to_s).join(', ') : arg.to_s
end
sprintf(details.message_format, *formatted_args)

begin
sprintf(I18n.translate(details.name, raise: true, :locale => I18n.locale), *formatted_args)
rescue I18n::MissingTranslationData => e
sprintf(details.message_format, *formatted_args)
end
end

def code
Expand Down
4 changes: 4 additions & 0 deletions spec/fixtures/i18n/en_US.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
en_US:
ServiceInvalid: "This is a translated message of %s %s."
MessagePartialTranslated: "This is a translated message of %s %s only in default locale"
3 changes: 3 additions & 0 deletions spec/fixtures/i18n/zh_CN.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
zh_CN:
ServiceInvalid: "这是一条被翻译的信息:%s %s。"
7 changes: 7 additions & 0 deletions spec/unit/controllers/base/base_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
let(:token_decoder) { double(:decoder) }
let(:header_token) { 'some token' }
let(:token_info) { {'user_id' => 'some user'} }
let(:env) { {'HTTP_ACCEPT_LANGUAGE' => 'en_US'} }

before do
configurer = VCAP::CloudController::Security::SecurityContextConfigurer.new(token_decoder)
Expand All @@ -27,6 +28,12 @@
subject.dispatch(:to_s, [:a, :b])
end

it "should record the locale during dispatching the request" do
expect(subject).to receive(:to_s).with([:a, :b])
subject.dispatch(:to_s, [:a, :b])
expect(I18n.locale).to eq(:en_US)
end

it "should log a debug message" do
expect(logger).to receive(:debug).with("cc.dispatch", endpoint: :to_s, args: [])
subject.dispatch(:to_s)
Expand Down
4 changes: 4 additions & 0 deletions spec/unit/lib/cloud_controller/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ module VCAP::CloudController
it "sets a default value for app_bits_upload_grace_period_in_seconds" do
expect(config[:app_bits_upload_grace_period_in_seconds]).to eq(0)
end

it "sets a default value for default_loacle" do
expect(config[:default_locale]).to eq("en_US")
end
end

context "when config values are provided" do
Expand Down
9 changes: 6 additions & 3 deletions spec/unit/lib/cloud_controller/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ module VCAP::CloudController
let (:argv_options) { [] }

before do
allow_any_instance_of(Runner).to receive(:parse_config)
allow_any_instance_of(Runner).to receive(:deprecation_warning)
end

Expand All @@ -290,10 +289,10 @@ module VCAP::CloudController
describe "Configuration File" do
["-c", "--config"].each do |flag|
describe flag do
let (:argv_options) { [flag, "config/minimal_config.yml"] }
let (:argv_options) { [flag, config_file.path] }

it "should set the configuration file" do
expect(subject.config_file).to eq("config/minimal_config.yml")
expect(subject.config_file).to eq(config_file.path)
end
end
end
Expand All @@ -318,6 +317,10 @@ module VCAP::CloudController
end
end
end

it "should initialize the i18n framework" do
expect(I18n.default_locale).to eq(:en_US)
end
end

describe "#start_thin_server" do
Expand Down
63 changes: 50 additions & 13 deletions spec/unit/lib/vcap/errors/api_error_spec.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
# coding: utf-8

require 'spec_helper'

module VCAP::Errors
describe ApiError do
let(:details) do
def create_details(message)
double(Details,
name: "ServiceInvalid",
name: message,
response_code: 400,
code: 12345,
message_format: "Before %s %s after.")
end

let(:name) { "ServiceInvalid" }
let(:messageServiceInvalid) { "ServiceInvalid" }
let(:messagePartialTranslated) { "MessagePartialTranslated"}
let(:messageNotTranslated) { "MessageNotTranslated" }
let(:args) { [ "foo", "bar" ] }

let(:messageServiceInvalidDetails) { create_details(messageServiceInvalid) }
let(:messagePartialTranslatedDetails) { create_details(messagePartialTranslated) }
let(:messageNotTranslatedDetails) { create_details(messageNotTranslated) }

I18n.default_locale = :en_US
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.load_path = Dir[File.expand_path("../../../../fixtures/i18n/*.yml", File.dirname(__FILE__))]

before do
allow(Details).to receive("new").with(name).and_return(details)
allow(Details).to receive("new").with(messageServiceInvalid).and_return(messageServiceInvalidDetails)
allow(Details).to receive("new").with(messagePartialTranslated).and_return(messagePartialTranslatedDetails)
allow(Details).to receive("new").with(messageNotTranslated).and_return(messageNotTranslatedDetails)
end

context ".new_from_details" do
let(:args) { [ "foo", "bar" ] }

subject(:api_error) { ApiError.new_from_details(name, *args) }
subject(:api_error) { ApiError.new_from_details(messageServiceInvalid, *args) }

it "returns an ApiError" do
expect(api_error).to be_a(ApiError)
Expand All @@ -29,12 +42,8 @@ module VCAP::Errors
expect(api_error).to be_a(Exception)
end

it "sets the message using the format provided in the v2.yml" do
expect(api_error.message).to eq("Before foo bar after.")
end

context "if it doesn't recognise the error from v2.yml" do
let(:name) { "What is this? I don't know?!!"}
let(:messageServiceInvalid) { "What is this? I don't know?!!"}

before do
allow(Details).to receive(:new).and_call_original
Expand All @@ -46,11 +55,39 @@ module VCAP::Errors
end
end

context "get error message" do
subject(:api_error) { ApiError.new_from_details(messageServiceInvalid, *args) }
subject(:api_error_with_partial_translation) { ApiError.new_from_details(messagePartialTranslated, *args) }
subject(:api_error_with_translation_missing) { ApiError.new_from_details(messageNotTranslated, *args) }

it "should translate the message based on the locale" do
I18n.locale = :en_US
expect(api_error.message).to eq("This is a translated message of foo bar.")
I18n.locale = :zh_CN
expect(api_error.message).to eq("这是一条被翻译的信息:foo bar。")
end

it "should use the default locale message when the message is not translated in target locale" do
I18n.locale = :zh_CN
expect(api_error_with_partial_translation.message).to eq("This is a translated message of foo bar only in default locale")
end

it "should use the default locale message when the locale is not recognized" do
I18n.locale = :unknown_locale
expect(api_error.message).to eq("This is a translated message of foo bar.")
end

it "should use the original message when the translation is missing" do
I18n.locale = :en_US
expect(api_error_with_translation_missing.message).to eq("Before foo bar after.")
end
end

context "with details" do
subject(:api_error) { ApiError.new }

before do
api_error.details = details
api_error.details = messageServiceInvalidDetails
end

it "exposes the code" do
Expand Down

0 comments on commit f171613

Please sign in to comment.