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'
|
2015-12-13 22:20:48 +01:00
|
|
|
option :locales, nil, 'List of locales, will autodiscover by default'
|
|
|
|
option :langs, nil, 'Backwards compatibility if old option name. Use `locales` instead.'
|
|
|
|
option :locale_map, {}, 'Locale shortname map'
|
|
|
|
option :lang_map, nil, 'Backwards compatibility if old option name. Use `locale_map` instead.'
|
2013-12-28 01:26:31 +01:00
|
|
|
option :path, '/:locale/', 'URL prefix path'
|
|
|
|
option :templates_dir, 'localizable', 'Location of templates to be localized'
|
2015-12-13 22:20:48 +01:00
|
|
|
option :mount_at_root, nil, 'Mount a specific locale at the root of the site'
|
2013-12-28 01:26:31 +01:00
|
|
|
option :data, 'locales', 'The directory holding your locale configurations'
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
# Exposes `locales` to templates
|
2015-12-14 19:22:27 +01:00
|
|
|
expose_to_template :locales, :langs, :locale, :lang
|
2015-05-02 20:48:21 +02:00
|
|
|
|
2015-05-04 00:38:18 +02:00
|
|
|
def initialize(*)
|
|
|
|
super
|
2015-05-04 02:11:49 +02:00
|
|
|
|
2015-05-04 00:38:18 +02:00
|
|
|
require 'i18n'
|
|
|
|
|
2015-12-13 22:29:33 +01:00
|
|
|
options[:locales] = options[:langs] unless options[:langs].nil?
|
2015-12-13 22:20:48 +01:00
|
|
|
|
2015-12-13 22:29:33 +01:00
|
|
|
options[:locale_map] = options[:lang_map] unless options[:lang_map].nil?
|
2015-12-13 22:20:48 +01:00
|
|
|
|
2015-05-04 00:38:18 +02:00
|
|
|
# Don't fail on invalid locale, that's not what our current
|
|
|
|
# users expect.
|
|
|
|
::I18n.enforce_available_locales = false
|
|
|
|
|
|
|
|
# This is for making the tests work - since the tests
|
|
|
|
# don't completely reload middleman, I18n.load_path can get
|
|
|
|
# polluted with paths from other test app directories that don't
|
|
|
|
# exist anymore.
|
|
|
|
app.after_configuration_eval do
|
|
|
|
::I18n.load_path.delete_if { |path| path =~ %r{tmp/aruba} }
|
|
|
|
::I18n.reload!
|
|
|
|
end if ENV['TEST']
|
|
|
|
end
|
|
|
|
|
2014-07-06 01:50:19 +02:00
|
|
|
def after_configuration
|
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
|
|
|
|
2014-03-26 00:54:16 +01:00
|
|
|
locales_file_path = options[:data]
|
2013-03-20 21:46:20 +01:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
# Tell the file watcher to observe the :data_dir
|
|
|
|
app.files.watch :locales,
|
|
|
|
path: File.join(app.root, locales_file_path),
|
2015-02-27 02:08:40 +01:00
|
|
|
only: /.*(rb|yml|yaml)$/
|
2012-03-11 04:40:04 +01:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
# Setup data files before anything else so they are available when
|
|
|
|
# parsing config.rb
|
2014-12-23 23:54:21 +01:00
|
|
|
app.files.on_change(:locales, &method(:on_file_changed))
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
@maps = {}
|
2015-12-13 22:20:48 +01:00
|
|
|
@mount_at_root = options[:mount_at_root].nil? ? locales.first : options[:mount_at_root]
|
2013-04-07 02:21:56 +02:00
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
configure_i18n
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
logger.info "== Locales: #{locales.join(', ')} (Default #{@mount_at_root})"
|
2013-04-20 23:58:06 +02:00
|
|
|
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
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-05-16 22:21:12 +02:00
|
|
|
def url_for(path_or_resource, options={})
|
2015-05-16 21:50:39 +02:00
|
|
|
locale = options.delete(:locale) || ::I18n.locale
|
2015-05-16 22:21:12 +02:00
|
|
|
|
2015-06-02 22:48:23 +02:00
|
|
|
opts = options.dup
|
2015-06-01 22:49:14 +02:00
|
|
|
|
2015-06-02 22:48:23 +02:00
|
|
|
should_relativize = opts.key?(:relative) ? opts[:relative] : config[:relative_links]
|
2015-06-01 22:49:14 +02:00
|
|
|
|
2015-06-02 22:48:23 +02:00
|
|
|
opts[:relative] = false
|
|
|
|
|
|
|
|
href = super(path_or_resource, opts)
|
2015-06-01 20:51:27 +02:00
|
|
|
|
2015-06-01 22:49:14 +02:00
|
|
|
final_path = if result = extensions[:i18n].localized_path(href, locale)
|
2015-06-01 20:51:27 +02:00
|
|
|
result
|
|
|
|
else
|
|
|
|
# Should we log the missing file?
|
|
|
|
href
|
|
|
|
end
|
2015-06-01 22:49:14 +02:00
|
|
|
|
2015-06-02 22:48:23 +02:00
|
|
|
opts[:relative] = should_relativize
|
|
|
|
|
|
|
|
begin
|
|
|
|
super(final_path, opts)
|
|
|
|
rescue RuntimeError
|
|
|
|
super(path_or_resource, options)
|
|
|
|
end
|
2015-05-11 18:13:04 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-02-24 20:06:28 +01:00
|
|
|
def locate_partial(partial_name, try_static=false)
|
2014-08-14 19:14:26 +02:00
|
|
|
locals_dir = extensions[:i18n].options[:templates_dir]
|
|
|
|
|
|
|
|
# Try /localizable
|
|
|
|
partials_path = File.join(locals_dir, partial_name)
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
locale_suffix = ::I18n.locale
|
2014-08-14 19:14:26 +02:00
|
|
|
|
|
|
|
extname = File.extname(partial_name)
|
|
|
|
maybe_static = extname.length > 0
|
|
|
|
suffixed_partial_name = if maybe_static
|
2015-12-13 22:20:48 +01:00
|
|
|
partial_name.sub(extname, ".#{locale_suffix}#{extname}")
|
2014-08-14 19:14:26 +02:00
|
|
|
else
|
2015-12-13 22:20:48 +01:00
|
|
|
"#{partial_name}.#{locale_suffix}"
|
2014-08-14 19:14:26 +02:00
|
|
|
end
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
if locale_suffix
|
2015-02-24 20:06:28 +01:00
|
|
|
super(suffixed_partial_name, maybe_static) ||
|
|
|
|
super(File.join(locals_dir, suffixed_partial_name), maybe_static) ||
|
|
|
|
super(partials_path, try_static) ||
|
2014-11-19 18:04:56 +01:00
|
|
|
super
|
2014-08-14 19:14:26 +02:00
|
|
|
else
|
2015-02-24 20:06:28 +01:00
|
|
|
super(partials_path, try_static) ||
|
2014-11-19 18:04:56 +01:00
|
|
|
super
|
2014-08-14 19:14:26 +02:00
|
|
|
end
|
|
|
|
end
|
2013-04-20 23:58:06 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-04-24 19:26:42 +02:00
|
|
|
Contract ArrayOf[Symbol]
|
2015-12-13 22:20:48 +01:00
|
|
|
def locales
|
|
|
|
@locales ||= known_locales
|
2013-04-20 23:58:06 +02:00
|
|
|
end
|
2013-10-29 00:42:08 +01:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
# Backwards API compat
|
2016-01-14 20:21:42 +01:00
|
|
|
alias langs locales
|
2015-12-13 22:20:48 +01:00
|
|
|
|
2015-12-14 19:22:27 +01:00
|
|
|
Contract Symbol
|
|
|
|
def locale
|
|
|
|
::I18n.locale
|
|
|
|
end
|
|
|
|
|
|
|
|
# Backwards API compat
|
2016-01-14 20:21:42 +01:00
|
|
|
alias lang locale
|
2015-12-14 19:22:27 +01:00
|
|
|
|
2013-04-20 23:58:06 +02:00
|
|
|
# Update the main sitemap resource list
|
2014-07-03 04:04:34 +02:00
|
|
|
# @return Array<Middleman::Sitemap::Resource>
|
|
|
|
Contract ResourceList => ResourceList
|
2013-04-20 23:58:06 +02:00
|
|
|
def manipulate_resource_list(resources)
|
|
|
|
new_resources = []
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-05-16 22:44:00 +02:00
|
|
|
file_extension_resources = resources.select do |resource|
|
|
|
|
parse_locale_extension(resource.path)
|
|
|
|
end
|
|
|
|
|
|
|
|
localizable_folder_resources = resources.select do |resource|
|
|
|
|
!file_extension_resources.include?(resource) && File.fnmatch?(File.join(options[:templates_dir], '**'), resource.path)
|
|
|
|
end
|
|
|
|
|
|
|
|
# If it's a "localizable template"
|
2015-09-28 23:52:16 +02:00
|
|
|
localizable_folder_resources.each do |resource|
|
2015-05-16 22:44:00 +02:00
|
|
|
page_id = File.basename(resource.path, File.extname(resource.path))
|
2015-12-13 22:20:48 +01:00
|
|
|
locales.each do |locale|
|
2015-05-16 22:44:00 +02:00
|
|
|
# Remove folder name
|
|
|
|
path = resource.path.sub(options[:templates_dir], '')
|
2015-12-13 22:20:48 +01:00
|
|
|
new_resources << build_resource(path, resource.path, page_id, locale)
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2014-04-28 07:24:27 +02:00
|
|
|
|
2015-09-28 23:52:16 +02:00
|
|
|
resource.ignore!
|
|
|
|
|
2014-04-28 07:24:27 +02:00
|
|
|
# This is for backwards compatibility with the old provides_metadata-based code
|
|
|
|
# that used to be in this extension, but I don't know how much sense it makes.
|
2015-12-13 22:20:48 +01:00
|
|
|
# next if resource.options[:locale]
|
2014-07-02 20:05:57 +02:00
|
|
|
|
2015-09-28 23:52:16 +02:00
|
|
|
# $stderr.puts "Defaulting #{resource.path} to #{@mount_at_root}"
|
2015-12-13 22:20:48 +01:00
|
|
|
# resource.add_metadata options: { locale: @mount_at_root }, locals: { locale: @mount_at_root }
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-05-16 22:44:00 +02:00
|
|
|
# If it uses file extension localization
|
2015-09-28 23:52:16 +02:00
|
|
|
file_extension_resources.each do |resource|
|
2015-05-16 22:44:00 +02:00
|
|
|
result = parse_locale_extension(resource.path)
|
2015-12-13 22:20:48 +01:00
|
|
|
ext_locale, path, page_id = result
|
|
|
|
new_resources << build_resource(path, resource.path, page_id, ext_locale)
|
2015-09-28 23:52:16 +02:00
|
|
|
|
|
|
|
resource.ignore!
|
2015-05-16 22:44:00 +02:00
|
|
|
end
|
|
|
|
|
2015-05-11 18:13:04 +02:00
|
|
|
@lookup = new_resources.each_with_object({}) do |desc, sum|
|
|
|
|
abs_path = desc.source_path.sub(options[:templates_dir], '')
|
|
|
|
sum[abs_path] ||= {}
|
2015-12-13 22:20:48 +01:00
|
|
|
sum[abs_path][desc.locale] = '/' + desc.path
|
2015-05-11 18:13:04 +02:00
|
|
|
end
|
|
|
|
|
2016-01-03 02:37:11 +01:00
|
|
|
new_resources.reduce(resources) do |sum, r|
|
|
|
|
r.execute_descriptor(app, sum)
|
|
|
|
end
|
2015-05-11 18:13:04 +02:00
|
|
|
end
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
Contract String, Symbol => String
|
|
|
|
def localized_path(path, locale)
|
2015-06-01 20:51:27 +02:00
|
|
|
lookup_path = path.dup
|
|
|
|
lookup_path << app.config[:index_file] if lookup_path.end_with?('/')
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
@lookup[lookup_path] && @lookup[lookup_path][locale]
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2013-04-03 08:28:47 +02:00
|
|
|
|
2016-01-05 19:58:11 +01:00
|
|
|
Contract Symbol => String
|
|
|
|
def path_root(locale)
|
|
|
|
if (options[:mount_at_root] == locale) || (options[:mount_at_root].nil? && locales[0] == locale)
|
|
|
|
'/'
|
|
|
|
else
|
|
|
|
replacement = options[:locale_map].fetch(locale, locale)
|
|
|
|
options[:path].sub(':locale', replacement.to_s).sub(':lang', replacement.to_s) # Backward compat
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
private
|
|
|
|
|
2014-07-16 03:01:45 +02:00
|
|
|
def on_file_changed(_updated_files, _removed_files)
|
2015-11-28 00:26:46 +01:00
|
|
|
::I18n.load_path |= app.files.by_type(:locales).files.map { |p| p[:full_path].to_s }
|
2014-07-16 03:01:45 +02:00
|
|
|
::I18n.reload!
|
2013-05-29 20:00:37 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def configure_i18n
|
2015-11-28 00:26:46 +01:00
|
|
|
::I18n.load_path |= app.files.by_type(:locales).files.map { |p| p[:full_path].to_s }
|
2013-05-29 20:00:37 +02:00
|
|
|
::I18n.reload!
|
|
|
|
|
|
|
|
::I18n.default_locale = @mount_at_root
|
2014-07-02 19:11:52 +02:00
|
|
|
|
2013-05-29 20:00:37 +02:00
|
|
|
# Reset fallbacks to fall back to our new default
|
2014-07-02 19:11:52 +02:00
|
|
|
::I18n.fallbacks = ::I18n::Locale::Fallbacks.new if ::I18n.respond_to?(:fallbacks)
|
2013-05-29 20:00:37 +02:00
|
|
|
end
|
|
|
|
|
2015-04-24 19:26:42 +02:00
|
|
|
Contract ArrayOf[Symbol]
|
2015-12-13 22:20:48 +01:00
|
|
|
def known_locales
|
|
|
|
if options[:locales]
|
|
|
|
Array(options[:locales]).map(&:to_sym)
|
2013-05-29 20:00:37 +02:00
|
|
|
else
|
2015-12-13 22:20:48 +01:00
|
|
|
known_locales = app.files.by_type(:locales).files.select do |p|
|
2014-07-16 03:01:45 +02:00
|
|
|
p[:relative_path].to_s.split(File::SEPARATOR).length == 1
|
2014-04-29 01:02:18 +02:00
|
|
|
end
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
known_locales.map do |p|
|
2014-07-16 03:01:45 +02:00
|
|
|
File.basename(p[:relative_path].to_s).sub(/\.ya?ml$/, '').sub(/\.rb$/, '')
|
2015-05-16 22:21:12 +02:00
|
|
|
end.sort.map(&:to_sym)
|
2013-05-29 20:00:37 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
# Parse locale extension filename
|
2015-12-13 22:20:48 +01:00
|
|
|
# @return [locale, path, basename]
|
2013-03-20 21:46:20 +01:00
|
|
|
# will return +nil+ if no locale extension
|
2014-07-03 04:04:34 +02:00
|
|
|
Contract String => Maybe[[Symbol, String, String]]
|
2013-03-20 21:46:20 +01:00
|
|
|
def parse_locale_extension(path)
|
2013-05-03 07:03:28 +02:00
|
|
|
path_bits = path.split('.')
|
|
|
|
return nil if path_bits.size < 3
|
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
locale = path_bits.delete_at(-2).to_sym
|
|
|
|
return nil unless locales.include?(locale)
|
2013-05-03 07:03:28 +02:00
|
|
|
|
|
|
|
path = path_bits.join('.')
|
|
|
|
basename = File.basename(path_bits[0..-2].join('.'))
|
2015-12-13 22:20:48 +01:00
|
|
|
[locale, path, basename]
|
2013-03-20 21:46:20 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
LocalizedPageDescriptor = Struct.new(:path, :source_path, :locale) do
|
2016-01-03 02:37:11 +01:00
|
|
|
def execute_descriptor(app, resources)
|
2015-09-17 22:53:43 +02:00
|
|
|
r = ::Middleman::Sitemap::ProxyResource.new(app.sitemap, path, source_path)
|
2015-12-13 22:20:48 +01:00
|
|
|
r.add_metadata options: { locale: locale }
|
2016-01-03 02:37:11 +01:00
|
|
|
resources + [r]
|
2015-05-11 18:13:04 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-17 22:53:43 +02:00
|
|
|
Contract String, String, String, Symbol => LocalizedPageDescriptor
|
2015-12-13 22:20:48 +01:00
|
|
|
def build_resource(path, source_path, page_id, locale)
|
2013-05-03 06:17:50 +02:00
|
|
|
old_locale = ::I18n.locale
|
2015-12-13 22:20:48 +01:00
|
|
|
::I18n.locale = locale
|
2014-04-29 19:50:21 +02:00
|
|
|
localized_page_id = ::I18n.t("paths.#{page_id}", default: page_id, fallback: [])
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2015-07-20 01:45:45 +02:00
|
|
|
partially_localized_path = ''
|
2015-03-19 21:20:19 +01:00
|
|
|
|
|
|
|
File.dirname(path).split('/').each do |path_sub|
|
2015-07-20 01:45:45 +02:00
|
|
|
next if path_sub == ''
|
2016-01-14 20:21:42 +01:00
|
|
|
partially_localized_path = "#{partially_localized_path}/#{::I18n.t("paths.#{path_sub}", default: path_sub)}"
|
2015-03-19 21:20:19 +01:00
|
|
|
end
|
|
|
|
|
2015-06-01 20:51:27 +02:00
|
|
|
path = "#{partially_localized_path}/#{File.basename(path)}"
|
2015-03-19 21:20:19 +01:00
|
|
|
|
2016-01-05 19:58:11 +01:00
|
|
|
prefix = path_root(locale)
|
2013-04-20 23:58:06 +02:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
# path needs to be changed if file has a localizable extension. (options[mount_at_root] == locale)
|
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
|
|
|
|
2014-04-28 08:05:18 +02:00
|
|
|
path = path.sub(options[:templates_dir] + '/', '')
|
2013-10-29 00:42:08 +01:00
|
|
|
|
2013-03-20 21:46:20 +01:00
|
|
|
::I18n.locale = old_locale
|
2015-05-11 18:13:04 +02:00
|
|
|
|
2015-12-13 22:20:48 +01:00
|
|
|
LocalizedPageDescriptor.new(path, source_path, locale)
|
2012-03-11 04:40:04 +01:00
|
|
|
end
|
2012-06-06 04:11:05 +02:00
|
|
|
end
|