Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions middleman-core/features/i18n_paths.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Feature: i18n Paths

Scenario: Linking to a page in a language-agnostic way
Given a fixture app "i18n-test-app"
And a file named "config.rb" with:
"""
activate :i18n
"""
Given the Server is running at "i18n-test-app"
When I go to "/"
Then I should see '<a href="/morning.html">'
When I go to "/es"
Then I should see '<a href="/es/manana.html">'
When I go to "/morning.html"
Then I should see "Good morning"
When I go to "/es/manana.html"
Then I should see "Buenos días"

Scenario: Locale-switching link
Given a fixture app "i18n-test-app"
And a file named "config.rb" with:
"""
activate :i18n
"""
Given the Server is running at "i18n-test-app"
When I go to "/"
Then I should see '<a href="/es/">es</a>'
When I go to "/es"
Then I should see '<a href="/">en</a>'
When I go to "/morning.html"
Then I should see '<a href="/es/manana.html">es</a>'
When I go to "/es/manana.html"
Then I should see '<a href="/morning.html">en</a>'
3 changes: 2 additions & 1 deletion middleman-core/fixtures/i18n-test-app/locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
en:
greetings: "Howdy"
hi: "Hello"
hi: "Hello"
morning: "Morning"
3 changes: 2 additions & 1 deletion middleman-core/fixtures/i18n-test-app/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ es:
hello: "hola"
morning: "manana"
one: "una"

greetings: "Como Esta?"
hi: "Hola"
morning: "Manana"
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
<%= stylesheet_link_tag :site %>
</head>
<body>
<nav>
<%= link_to t(:morning), "/morning.html" %>
<%= link_to other_locale, current_page, locale: other_locale %>
</nav>
<%= yield %>
</body>
</html>
</html>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: morning
---
Buenos días
228 changes: 154 additions & 74 deletions middleman-core/lib/middleman-core/core_extensions/i18n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension

# Exposes `locales` to templates
expose_to_template :locales, :langs, :locale, :lang
attr_reader :lookup

def initialize(*)
super
Expand Down Expand Up @@ -53,7 +54,6 @@ def after_configuration
# parsing config.rb
app.files.on_change(:locales, &method(:on_file_changed))

@maps = {}
@mount_at_root = options[:mount_at_root].nil? ? locales.first : options[:mount_at_root]

configure_i18n
Expand All @@ -69,27 +69,58 @@ def t(*args)
def url_for(path_or_resource, options={})
locale = options.delete(:locale) || ::I18n.locale

# Backup options in case there's an error
opts = options.dup

should_relativize = opts.key?(:relative) ? opts[:relative] : config[:relative_links]

opts[:relative] = false
should_relativize = if opts.key?(:relative)
opts[:relative]
else
config[:relative_links]
end

href = super(path_or_resource, opts)
opts[:relative] = false
# We will call super at first without relative
# until we figure out what's going on


# Me too, I have no idea if what I got is a path or resource.
# But I need the potential resource to figure out the page_id,
# if there's one, and if so, see if there's a localization
# available

# Look if this stringish designates a resource
# Copied from M::Util::Paths#url_for:153
if path_or_resource.is_a?(String) || path_or_resource.is_a?(Symbol)
if r = app.sitemap.find_resource_by_page_id(path_or_resource)
path_or_resource = r
elsif r = app.sitemap.find_resource_by_path(path_or_resource)
path_or_resource = r
end
end

final_path = if result = extensions[:i18n].localized_path(href, locale)
result
# If stringish designates a resource, transform p_o_r into
# the path with locale
if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
page_id = path_or_resource.page_id

final_path =
if result = extensions[:i18n].localized_path(page_id, locale)
result
else
# Should we log the missing file?
path_or_resource
end
else
# Should we log the missing file?
href
final_path = path_or_resource
end

opts[:relative] = should_relativize

begin
super(final_path, opts)
rescue RuntimeError
super(path_or_resource, options)
# rescue RuntimeError
# super(path_or_resource, options)
end
end

Expand Down Expand Up @@ -119,6 +150,19 @@ def locate_partial(partial_name, try_static=false)
super
end
end

def other_locale
if extensions[:i18n].langs.count > 2
raise "There is more than one other locale to choose"\
"from. Use `other_locales` to get them all."
end

other_locales[0]
end

def other_locales
extensions[:i18n].langs - [I18n.locale]
end
end

Contract ArrayOf[Symbol]
Expand All @@ -141,70 +185,116 @@ def locale
# @return Array<Middleman::Sitemap::Resource>
Contract ResourceList => ResourceList
def manipulate_resource_list(resources)
@lookup ||= {}

new_resources = []

file_extension_resources = resources.select do |resource|
parse_locale_extension(resource.path)
[FileExtensionResource, LocalizableResource].map do |type|
new_resources << type.find_in(resources, i18n: self).map(&:build)
end

localizable_folder_resources = resources.select do |resource|
!file_extension_resources.include?(resource) && File.fnmatch?(File.join(options[:templates_dir], '**'), resource.path)
new_resources.flatten.reduce(resources) do |sum, r|
r.execute_descriptor(app, sum)
end
end

# If it's a "localizable template"
localizable_folder_resources.each do |resource|
page_id = File.basename(resource.path, File.extname(resource.path))
locales.each do |locale|
# Remove folder name
path = resource.path.sub(options[:templates_dir], '')
new_resources << build_resource(path, resource.path, page_id, locale)
end
Contract String, Symbol => Maybe[String]
def localized_path(page_id, locale)
@lookup[page_id] && @lookup[page_id][locale]
end

resource.ignore!
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][locale] || locale
options[:path].sub(':locale', replacement.to_s).sub(':lang', replacement.to_s) # Backward compat
end
end

# Those are 'source' resource not destination
class I18nResource
attr_reader :resource, :i18n

# 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.
# next if resource.options[:locale]
def initialize(resource, i18n)
@resource = resource
@i18n = i18n
end
end

# $stderr.puts "Defaulting #{resource.path} to #{@mount_at_root}"
# resource.add_metadata options: { locale: @mount_at_root }, locals: { locale: @mount_at_root }
class LocalizableResource < I18nResource
def self.find_in(resources, i18n:)
resources.each_with_object([]) do |resource, output|
is_localizable = File.fnmatch?(
File.join(i18n.options[:templates_dir], '**'), resource.path
)

output << new(resource, i18n) if !resource.ignored? && is_localizable
end
end

# If it uses file extension localization
file_extension_resources.each do |resource|
result = parse_locale_extension(resource.path)
ext_locale, path, page_id = result
new_resources << build_resource(path, resource.path, page_id, ext_locale)
def build
# Remove folder name
path = resource.path.sub(i18n.options[:templates_dir], '')
page_id = resource.page_id.sub(i18n.options[:templates_dir] + '/', '')

result = i18n.locales.map do |locale|
i18n.send(:build_resource, *[path, resource.path, page_id, locale])
end

resource.ignore!

result
end
end

@lookup = new_resources.each_with_object({}) do |desc, sum|
abs_path = desc.source_path.sub(options[:templates_dir], '')
sum[abs_path] ||= {}
sum[abs_path][desc.locale] = '/' + desc.path
class FileExtensionResource < I18nResource
def self.find_in(resources, i18n:)
# File extension resources are the ones that return a true parse
resources.each_with_object([]) do |resource, output|
output << new(resource, i18n) if parse_locale_extension(resource.path, i18n: i18n)[0]
end
end

new_resources.reduce(resources) do |sum, r|
r.execute_descriptor(app, sum)
def build
ext_locale, path = self.class.parse_locale_extension(resource.path, i18n: i18n)
_, page_id = self.class.parse_locale_extension(resource.page_id, i18n: i18n, has_last: false)

# result = i18n.build_resource(path, resource.path, page_id, ext_locale)
result = i18n.send(:build_resource, *[path, resource.path, page_id, ext_locale])
resource.ignore!

result
end
end

Contract String, Symbol => Maybe[String]
def localized_path(path, locale)
lookup_path = path.dup
lookup_path << app.config[:index_file] if lookup_path.end_with?('/')
# Parse locale extension filename or page_id when implicit
#
# Page id could have no extension if set manually
#
# @return [locale, result]
# will return +nil+ if no locale extension

@lookup[lookup_path] && @lookup[lookup_path][locale]
end
# Contract String, Hash => [Maybe[Symbol], String]
def self.parse_locale_extension(pathish, i18n:, has_last: true)
dirname = File.dirname(pathish)
basename = File.basename(pathish)

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][locale] || locale
options[:path].sub(':locale', replacement.to_s).sub(':lang', replacement.to_s) # Backward compat
path_bits = basename.split('.')

locale_index = has_last ? -2 : -1
locale = path_bits[locale_index].try(:to_sym)

if i18n.locales.include?(locale)
path_bits.delete_at(locale_index)
else
locale = nil
end

pathish = path_bits.join('.')
pathish = File.join(dirname, pathish) unless dirname == '.'

[locale, pathish]
end
end

Expand Down Expand Up @@ -242,40 +332,27 @@ def known_locales
end
end

# Parse locale extension filename
# @return [locale, path, basename]
# will return +nil+ if no locale extension
Contract String => Maybe[[Symbol, String, String]]
def parse_locale_extension(path)
path_bits = path.split('.')
return nil if path_bits.size < 3

locale = path_bits.delete_at(-2).to_sym
return nil unless locales.include?(locale)

path = path_bits.join('.')
basename = File.basename(path_bits[0..-2].join('.'))
[locale, path, basename]
end

LocalizedPageDescriptor = Struct.new(:path, :source_path, :locale) do
LocalizedPageDescriptor = Struct.new(:path, :source_path, :page_id, :locale) do
def execute_descriptor(app, resources)
r = ::Middleman::Sitemap::ProxyResource.new(app.sitemap, path, source_path)
r.add_metadata options: { locale: locale }
r.add_metadata page: {id: page_id}, options: { locale: locale }
resources + [r]
end
end

# Build destination resource
Contract String, String, String, Symbol => LocalizedPageDescriptor
def build_resource(path, source_path, page_id, locale)
old_locale = ::I18n.locale
::I18n.locale = locale
localized_page_id = ::I18n.t("paths.#{page_id}", default: page_id, fallback: [])
localized_page_id = ::I18n.t("paths.#{page_id}", default: page_id,
fallback: [])

partially_localized_path = ''

File.dirname(path).split('/').each do |path_sub|
next if path_sub == ''
next if ['', '.'].include?(path_sub)

partially_localized_path = "#{partially_localized_path}/#{::I18n.t("paths.#{path_sub}", default: path_sub)}"
end

Expand All @@ -290,8 +367,11 @@ def build_resource(path, source_path, page_id, locale)

path = path.sub(options[:templates_dir] + '/', '')

@lookup[page_id] ||= {}
@lookup[page_id][locale] = '/' + path

::I18n.locale = old_locale

LocalizedPageDescriptor.new(path, source_path, locale)
LocalizedPageDescriptor.new(path, source_path, page_id, locale)
end
end