Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a46852504c | |||
| f3e17bb6a6 | |||
| d993676ca2 |
@@ -39,6 +39,7 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency 'rss', '~> 0.2.9'
|
||||
s.add_dependency 'therubyrhino', '~> 2.1.0'
|
||||
s.add_dependency 'webrick', '~> 1.7'
|
||||
s.add_dependency 'i18n', '~> 1.8'
|
||||
|
||||
s.add_development_dependency 'rack-test', '~> 0.6.3'
|
||||
s.add_development_dependency 'shoulda', '~> 3.6.0'
|
||||
|
||||
+4
-1
@@ -5,6 +5,7 @@ require 'digest/sha1'
|
||||
require 'ostruct'
|
||||
|
||||
# external
|
||||
require 'i18n'
|
||||
require 'github/markup'
|
||||
require 'rhino' if RUBY_PLATFORM == 'java'
|
||||
|
||||
@@ -14,6 +15,9 @@ require File.expand_path('../gollum/uri_encode_component', __FILE__)
|
||||
module Gollum
|
||||
VERSION = '5.2.3'
|
||||
|
||||
::I18n.available_locales = [:en]
|
||||
::I18n.load_path = Dir[File.expand_path("lib/gollum/locales") + "/*.yml"]
|
||||
|
||||
def self.assets_path
|
||||
::File.expand_path('gollum/public', ::File.dirname(__FILE__))
|
||||
end
|
||||
@@ -32,5 +36,4 @@ module Gollum
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -15,6 +15,7 @@ require 'pathname'
|
||||
require 'gollum'
|
||||
require 'gollum/assets'
|
||||
require 'gollum/views/helpers'
|
||||
require 'gollum/views/helpers/locale_helpers'
|
||||
require 'gollum/views/layout'
|
||||
require 'gollum/views/editable'
|
||||
require 'gollum/views/has_page'
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
module Precious
|
||||
module Views
|
||||
module LocaleHelpers
|
||||
NO_METHOD_MESSAGE = 'Argument must be a view method'
|
||||
YAML_VARIABLE_REGEXP = /\%\{[\w]+\}/
|
||||
|
||||
# Returns all I18n translation strings for the current view class.
|
||||
# This method support YAML arguments. For example:
|
||||
#
|
||||
# last_edited: This content was last edited at %{date}.
|
||||
#
|
||||
# Where the `date` argument must be a method available on the current
|
||||
# class.
|
||||
#
|
||||
# Use this interface within Mustache templates to render any user
|
||||
# interface strings in the current locale. For example:
|
||||
#
|
||||
# {{ t.last_edited }}
|
||||
#
|
||||
def t
|
||||
autofill I18n.t(locale_klass_name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Recursively looks up I18n translation values and autofills any YAML
|
||||
# arguments with the return value of the current class's matching method.
|
||||
#
|
||||
# When a translation value with an argument has no matching method, we
|
||||
# then return that value transformed to include the `no_method_message`
|
||||
#
|
||||
def autofill(yaml)
|
||||
yaml.map { |i18n_key, i18n_value|
|
||||
if i18n_value.is_a? Hash
|
||||
[i18n_key, autofill(i18n_value)]
|
||||
elsif has_arguments?(i18n_value)
|
||||
fill_argument_content(i18n_key, i18n_value)
|
||||
else
|
||||
[i18n_key, i18n_value]
|
||||
end
|
||||
}.to_h
|
||||
end
|
||||
|
||||
def fill_argument_content(i18n_key, i18n_value)
|
||||
i18n_value.gsub!(YAML_VARIABLE_REGEXP) do |argument|
|
||||
method_name = argument.gsub(/[^\w]/, '')
|
||||
|
||||
next if method_name.nil?
|
||||
|
||||
begin
|
||||
self.public_send(method_name)
|
||||
rescue NoMethodError => error
|
||||
no_method_message(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
[i18n_key, i18n_value]
|
||||
end
|
||||
|
||||
def has_arguments?(i18n_value)
|
||||
i18n_value.match?(YAML_VARIABLE_REGEXP)
|
||||
end
|
||||
|
||||
# Returns the current class name in a format that is acceptable in YAML.
|
||||
# To summarize its function:
|
||||
#
|
||||
# NameOfConstant => name_of_constant
|
||||
#
|
||||
def locale_klass_name
|
||||
@locale_klass_name ||= self.class.name.gsub(/::/, '/').
|
||||
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
||||
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
||||
tr('-', '_').
|
||||
downcase
|
||||
end
|
||||
|
||||
def no_method_message(method_name, message = NO_METHOD_MESSAGE)
|
||||
"[#{message}: #{method_name}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,7 @@ module Precious
|
||||
include Rack::Utils
|
||||
include Sprockets::Helpers
|
||||
include Precious::Views::AppHelpers
|
||||
include Precious::Views::LocaleHelpers
|
||||
include Precious::Views::SprocketsHelpers
|
||||
include Precious::Views::RouteHelpers
|
||||
include Precious::Views::OcticonHelpers
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
require_relative "../../helper"
|
||||
require_relative "../../../lib/gollum/views/helpers"
|
||||
|
||||
describe Precious::Views::LocaleHelpers do
|
||||
class TestClass < Mustache
|
||||
include Precious::Views::LocaleHelpers
|
||||
|
||||
def author
|
||||
"J.R.R."
|
||||
end
|
||||
|
||||
def location
|
||||
"Bloemfontein"
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
::I18n.available_locales = [:en, :de]
|
||||
::I18n.load_path = Dir[File.expand_path("test/support/locales" + "/*.yml")]
|
||||
end
|
||||
|
||||
def teardown
|
||||
I18n.locale = :en
|
||||
end
|
||||
|
||||
let(:dummy_instance) { TestClass.new }
|
||||
|
||||
describe "#t" do
|
||||
describe "mustache usage" do
|
||||
let(:subject) { dummy_instance.render(mustache_template) }
|
||||
|
||||
let(:mustache_template) { "{{ t.hello_world }}" }
|
||||
|
||||
describe "in the default locale" do
|
||||
it "returns the translation string" do
|
||||
_(subject).must_equal "Hello world"
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the configured locale" do
|
||||
it "returns the translation string" do
|
||||
I18n.locale = :de
|
||||
|
||||
_(subject).must_equal "Hallo Welt"
|
||||
end
|
||||
end
|
||||
|
||||
describe "translations with YAML arguments" do
|
||||
let(:mustache_template) { "{{ t.author_info.full }}" }
|
||||
|
||||
describe "in the default locale" do
|
||||
it "autofills YAML arguments" do
|
||||
_(subject).must_equal "Author J.R.R. is from Bloemfontein"
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the configured locale" do
|
||||
it "autofills YAML arguments" do
|
||||
I18n.locale = :de
|
||||
|
||||
_(subject).must_equal "Autor J.R.R. ist vom Bloemfontein"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "translations with invalid arguments" do
|
||||
let(:mustache_template) { "{{ t.has_invalid_argument }}" }
|
||||
|
||||
it "fails gracefully with embedded error message" do
|
||||
expected_string = "Welcome to " \
|
||||
"[#{TestClass::NO_METHOD_MESSAGE}: no_matching_method]"
|
||||
|
||||
_(subject).must_equal expected_string
|
||||
end
|
||||
end
|
||||
|
||||
describe "out of scope translations" do
|
||||
let(:mustache_template) { "{{ t.never_called }}" }
|
||||
|
||||
it "does not include translation keys from other classes" do
|
||||
_(subject).must_be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "missing translations" do
|
||||
let(:mustache_template) { "{{ t.nested.nonexistent_key }}" }
|
||||
|
||||
it "outputs an empty string" do
|
||||
_(subject).must_be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "usage" do
|
||||
let(:subject) { dummy_instance.t }
|
||||
|
||||
it "returns a hash" do
|
||||
_(subject).must_be_kind_of Hash
|
||||
end
|
||||
|
||||
it "returns translation keys under 'test_class'" do
|
||||
i18n_keys = I18n.t("test_class").keys
|
||||
|
||||
_(subject.keys).must_equal i18n_keys
|
||||
end
|
||||
|
||||
it "does not return translation keys under other classes" do
|
||||
other_i18n_keys = I18n.t("nonexistant_test_class").keys
|
||||
|
||||
_(subject.keys).wont_include other_i18n_keys
|
||||
end
|
||||
|
||||
it "returns nested keys" do
|
||||
nested_keys = subject[:author_info].keys
|
||||
|
||||
_(nested_keys).must_equal [:full]
|
||||
end
|
||||
|
||||
describe "auto-filled YAML arguments" do
|
||||
let(:subject) { dummy_instance.t[:author_info][:full] }
|
||||
|
||||
it "auto-fills in the default locale" do
|
||||
_(subject).must_equal "Author J.R.R. is from Bloemfontein"
|
||||
end
|
||||
|
||||
it "auto-fills in a configured locale" do
|
||||
I18n.locale = :de
|
||||
|
||||
_(subject).must_equal "Autor J.R.R. ist vom Bloemfontein"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,7 @@ require 'shoulda'
|
||||
require 'mocha/setup'
|
||||
require 'fileutils'
|
||||
require 'minitest/reporters'
|
||||
require 'minitest/spec'
|
||||
require 'twitter_cldr'
|
||||
require 'tmpdir'
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
de:
|
||||
test_class:
|
||||
author_info:
|
||||
full: Autor %{author} ist vom %{location}
|
||||
has_invalid_argument: Willkommen in %{no_matching_method}
|
||||
hello_world: Hallo Welt
|
||||
nonexistant_test_class:
|
||||
never_called: Nie angerufen
|
||||
@@ -0,0 +1,8 @@
|
||||
en:
|
||||
test_class:
|
||||
author_info:
|
||||
full: Author %{author} is from %{location}
|
||||
has_invalid_argument: Welcome to %{no_matching_method}
|
||||
hello_world: Hello world
|
||||
nonexistant_test_class:
|
||||
never_called: Never called
|
||||
Reference in New Issue
Block a user