2013-04-20 23:58:06 +02:00
|
|
|
class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
2013-12-28 01:26:31 +01:00
|
|
|
option :no_fallbacks, false, 'Disable I18n fallbacks'
|
|
|
|
option :langs, nil, 'List of langs, will autodiscover by default'
|
|
|
|
option :lang_map, {}, 'Language shortname map'
|
|
|
|
option :path, '/:locale/', 'URL prefix path'
|
|
|
|
option :templates_dir, 'localizable', 'Location of templates to be localized'
|
|
|
|
option :mount_at_root, nil, 'Mount a specific language at the root of the site'
|
|
|
|
option :data, 'locales', 'The directory holding your locale configurations'
|
2013-04-20 23:58:06 +02:00
|
|
|
|
|
|
|
def initialize(app, options_hash={}, &block)
|
|
|
|
super
|
|
|
|
|
2013-11-15 12:26:27 +01:00
|
|
|
# TODO
|
|
|
|
# If :directory_indexes is already active,
|
|
|
|
# throw a warning explaining the bug and telling the use
|
|
|
|
# to reverse the order.
|
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
# See https://github.com/svenfuchs/i18n/wiki/Fallbacks
|
|
|
|
unless options[:no_fallbacks]
|
2013-12-28 01:26:31 +01:00
|
|
|
require 'i18n/backend/fallbacks'
|
2013-04-20 23:58:06 +02:00
|
|
|
::I18n::Backend::Simple.send(:include, ::I18n::Backend::Fallbacks)
|
|
|
|
end
|
2013-04-07 02:21:56 +02:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
app.config.define_setting :locales_dir, 'locales', 'The directory holding your locale configurations'
|
2013-03-20 21:46:20 +01:00
|
|
|
|
2013-05-03 06:17:50 +02:00
|
|
|
app.send :include, LocaleHelpers
|
2013-04-20 23:58:06 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
def after_configuration
|
2013-05-29 20:00:37 +02:00
|
|
|
app.files.reload_path(app.config[:locals_dir] || options[:data])
|
2012-03-11 04:40:04 +01:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
@locales_glob = File.join(app.config[:locals_dir] || options[:data], '**', '*.{rb,yml,yaml}')
|
2013-05-29 20:00:37 +02:00
|
|
|
@locales_regex = convert_glob_to_regex(@locales_glob)
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
@maps = {}
|
|
|
|
@mount_at_root = options[:mount_at_root].nil? ? langs.first : options[:mount_at_root]
|
2013-04-07 02:21:56 +02:00
|
|
|
|
2013-05-29 20:00:37 +02:00
|
|
|
configure_i18n
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
if !app.build?
|
|
|
|
logger.info "== Locales: #{langs.join(", ")} (Default #{@mount_at_root})"
|
|
|
|
end
|
2012-09-30 22:17:01 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
# Don't output localizable files
|
2013-12-28 01:26:31 +01:00
|
|
|
app.ignore File.join(options[:templates_dir], '**')
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-05-29 20:00:37 +02:00
|
|
|
app.sitemap.provides_metadata_for_path(&method(:metadata_for_path))
|
2013-04-20 23:58:06 +02:00
|
|
|
app.files.changed(&method(:on_file_changed))
|
|
|
|
app.files.deleted(&method(:on_file_changed))
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-05-03 06:17:50 +02:00
|
|
|
helpers do
|
2013-04-20 23:58:06 +02:00
|
|
|
def t(*args)
|
|
|
|
::I18n.t(*args)
|
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
delegate :logger, :to => :app
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
def langs
|
2013-05-29 20:00:37 +02:00
|
|
|
@_langs ||= get_known_languages
|
2013-04-20 23:58:06 +02:00
|
|
|
end
|
2013-10-29 00:42:08 +01:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
# Update the main sitemap resource list
|
|
|
|
# @return [void]
|
|
|
|
def manipulate_resource_list(resources)
|
|
|
|
@_localization_data = {}
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
new_resources = []
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
resources.each do |resource|
|
2013-11-19 09:17:50 +01:00
|
|
|
# If it uses file extension localization
|
2013-10-29 00:42:08 +01:00
|
|
|
if !parse_locale_extension(resource.path).nil?
|
|
|
|
result = parse_locale_extension(resource.path)
|
|
|
|
lang, path, page_id = result
|
|
|
|
new_resources << build_resource(path, resource.path, page_id, lang)
|
2013-05-03 06:17:50 +02:00
|
|
|
# If it's a "localizable template"
|
2013-12-28 01:26:31 +01:00
|
|
|
elsif File.fnmatch?(File.join(options[:templates_dir], '**'), resource.path)
|
2013-05-03 06:17:50 +02:00
|
|
|
page_id = File.basename(resource.path, File.extname(resource.path))
|
2013-03-20 21:46:20 +01:00
|
|
|
langs.each do |lang|
|
2013-05-03 06:17:50 +02:00
|
|
|
# Remove folder name
|
2013-12-28 01:26:31 +01:00
|
|
|
path = resource.path.sub(options[:templates_dir], '')
|
2013-05-03 06:17:50 +02:00
|
|
|
new_resources << build_resource(path, resource.path, page_id, lang)
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
resources + new_resources
|
|
|
|
end
|
2013-04-03 08:28:47 +02:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
private
|
|
|
|
|
2013-05-29 20:00:37 +02:00
|
|
|
|
|
|
|
def on_file_changed(file)
|
|
|
|
if @locales_regex =~ file
|
|
|
|
@_langs = nil # Clear langs cache
|
|
|
|
::I18n.reload!
|
|
|
|
end
|
|
|
|
end
|
2013-10-29 00:42:08 +01:00
|
|
|
|
2013-05-29 20:00:37 +02:00
|
|
|
def convert_glob_to_regex(glob)
|
|
|
|
# File.fnmatch doesn't support brackets: {rb,yml,yaml}
|
2013-12-28 01:26:31 +01:00
|
|
|
regex = @locales_glob.sub(/\./, '\.').sub(File.join('**', '*'), '.*').sub(/\//, '\/').sub('{rb,yml,yaml}', '(rb|ya?ml)')
|
2013-05-29 20:00:37 +02:00
|
|
|
%r{^#{regex}}
|
|
|
|
end
|
|
|
|
|
|
|
|
def configure_i18n
|
|
|
|
::I18n.load_path += Dir[File.join(app.root, @locales_glob)]
|
|
|
|
::I18n.reload!
|
|
|
|
|
|
|
|
::I18n.default_locale = @mount_at_root
|
|
|
|
# Reset fallbacks to fall back to our new default
|
|
|
|
if ::I18n.respond_to? :fallbacks
|
|
|
|
::I18n.fallbacks = ::I18n::Locale::Fallbacks.new
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def metadata_for_path(url)
|
|
|
|
if d = get_localization_data(url)
|
|
|
|
lang, page_id = d
|
|
|
|
else
|
|
|
|
# Default to the @mount_at_root lang
|
|
|
|
page_id = nil
|
|
|
|
lang = @mount_at_root
|
|
|
|
end
|
|
|
|
|
|
|
|
instance_vars = Proc.new do
|
|
|
|
@lang = lang
|
|
|
|
@page_id = page_id
|
|
|
|
end
|
|
|
|
|
|
|
|
locals = {
|
|
|
|
:lang => lang,
|
|
|
|
:page_id => page_id
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
:blocks => [instance_vars],
|
|
|
|
:locals => locals,
|
|
|
|
:options => { :lang => lang }
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_known_languages
|
|
|
|
if options[:langs]
|
|
|
|
Array(options[:langs]).map(&:to_sym)
|
|
|
|
else
|
|
|
|
known_langs = app.files.known_paths.select do |p|
|
|
|
|
p.to_s.match(@locales_regex) && (p.to_s.split(File::SEPARATOR).length === 2)
|
|
|
|
end.map { |p|
|
2013-12-28 01:26:31 +01:00
|
|
|
File.basename(p.to_s).sub(/\.ya?ml$/, '').sub(/\.rb$/, '')
|
2013-05-29 20:00:37 +02:00
|
|
|
}.sort.map(&:to_sym)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_localization_data(path)
|
|
|
|
@_localization_data ||= {}
|
|
|
|
@_localization_data[path]
|
|
|
|
end
|
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
# Parse locale extension filename
|
2013-05-03 07:03:28 +02:00
|
|
|
# @return [lang, path, basename]
|
2013-03-20 21:46:20 +01:00
|
|
|
# will return +nil+ if no locale extension
|
|
|
|
def parse_locale_extension(path)
|
2013-05-03 07:03:28 +02:00
|
|
|
path_bits = path.split('.')
|
|
|
|
return nil if path_bits.size < 3
|
|
|
|
|
|
|
|
lang = path_bits.delete_at(-2).to_sym
|
|
|
|
return nil unless langs.include?(lang)
|
|
|
|
|
|
|
|
path = path_bits.join('.')
|
|
|
|
basename = File.basename(path_bits[0..-2].join('.'))
|
|
|
|
[lang, path, basename]
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
def build_resource(path, source_path, page_id, lang)
|
2013-05-03 06:17:50 +02:00
|
|
|
old_locale = ::I18n.locale
|
|
|
|
::I18n.locale = lang
|
2013-03-20 21:46:20 +01:00
|
|
|
localized_page_id = ::I18n.t("paths.#{page_id}", :default => page_id, :fallback => [])
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2013-10-29 00:42:08 +01:00
|
|
|
prefix = if (options[:mount_at_root] == lang) || (options[:mount_at_root] == nil && langs[0] == lang)
|
2013-12-28 01:26:31 +01:00
|
|
|
'/'
|
2013-03-20 21:46:20 +01:00
|
|
|
else
|
|
|
|
replacement = options[:lang_map].fetch(lang, lang)
|
2013-12-28 01:26:31 +01:00
|
|
|
options[:path].sub(':locale', replacement.to_s)
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2013-10-29 00:42:08 +01:00
|
|
|
# path needs to be changed if file has a localizable extension. (options[mount_at_root] == lang)
|
2013-03-20 21:46:20 +01:00
|
|
|
path = ::Middleman::Util.normalize_path(
|
2013-06-01 02:46:12 +02:00
|
|
|
File.join(prefix, path.sub(page_id, localized_page_id))
|
2013-03-20 21:46:20 +01:00
|
|
|
)
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
path.gsub!(options[:templates_dir]+'/', '')
|
2013-10-29 00:42:08 +01:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
@_localization_data[path] = [lang, path, localized_page_id]
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
p = ::Middleman::Sitemap::Resource.new(app.sitemap, path)
|
2013-05-03 06:17:50 +02:00
|
|
|
p.proxy_to(source_path)
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
::I18n.locale = old_locale
|
|
|
|
p
|
2012-03-11 04:40:04 +01:00
|
|
|
end
|
2013-05-03 06:17:50 +02:00
|
|
|
|
|
|
|
module LocaleHelpers
|
|
|
|
# Access the list of languages supported by this Middleman application
|
|
|
|
# @return [Array<Symbol>]
|
|
|
|
def langs
|
|
|
|
extensions[:i18n].langs
|
|
|
|
end
|
|
|
|
end
|
2012-06-06 04:11:05 +02:00
|
|
|
end
|
2014-01-01 23:50:42 +01:00
|
|
|
|
|
|
|
Middleman::CoreExtensions::Internationalization.register(:i18n)
|