Start localizing the views (#1797)

* Internationalize `Views::Compare` templates

* Internationalize `Views::Error` templates

* Internationalize `Views::History` templates

* Internationalize `Views::LatestChanges` templates

* Internationalize `Views::Layout` templates

* Internationalize `Views::Overview` templates

* Internationalize `Views::Search` templates

* Reset I18n load path after I18n helper tests

* Create locale helper for global translations

There are some translation strings we should use across multiple views.
Instead of duplicating the translations, we can use a different locale
helper method, `#tt`, to get a hash of all available translations.

Then, in a partial view like `pagination.mustache`, we can render
translations regardless of what the current view class is.

This commit adds the necessary helper, tests, and uses the new method to
render translations on the `pagination.mustache` template, which is used
by many other view classes (latest changes, history, and search).
This commit is contained in:
benjamin wil
2022-07-30 17:01:05 -07:00
committed by GitHub
parent 98cb39347d
commit 738d6f6ec4
15 changed files with 320 additions and 107 deletions
+38
View File
@@ -0,0 +1,38 @@
en:
pagination:
aria:
label: Pagination
next_page: Next page
previous_page: Previous page
next: Next
previous: Previous
precious/views/compare:
back_to_page_history: Back to Page History
back_to_top: Back to Top
comparison_of: Comparison of
comparing_versions_of: Comparing versions of
comparing_from: "Comparing %{before} to %{after}"
revert: Revert Changes
precious/views/error:
error: Error
precious/views/history:
browse_in_history_description: Browse the page at this point in the history
compare_revisions: Compare Revisions
history_for: History for
precious/views/latest_changes:
title: Latest Changes (Globally)
precious/views/layout:
title: Home
precious/views/overview:
back_to_top: Back to Top
delete_confirmation: "Are you sure you want to delete %{name}?"
no_pages_in: There are no pages in
on: "on"
title: "Overview of %{ref}"
precious/views/search:
aria:
show_all: "Show all %{count} hits in this page"
back_to_top: Back to Top
no_results: There are no results for your search
search_results_for: Search results for
title: "Search results for %{query}"
+69 -40
View File
@@ -4,55 +4,84 @@
<h1 class="header-title text-center text-md-left py-4"> <h1 class="header-title text-center text-md-left py-4">
<span class="f1-light text-gray-light"> <span class="f1-light text-gray-light">
Comparing versions of {{t.comparing_versions_of}}
</span> </span>
{{name}} {{name}}
</h1> </h1>
</div> </div>
{{#message}} {{#message}}
<p>{{message}}</p> <p>{{message}}</p>
{{/message}} {{/message}}
<div id="compare-content"> <div id="compare-content">
<div class="py-4" id="actions"> <div class="py-4" id="actions">
{{#show_revert}} {{#show_revert}}
{{#allow_editing}} {{#allow_editing}}
<form name="gollum-revert" action="{{revert_path}}/{{escaped_url_path}}/{{before}}/{{after}}" method="post" id="gollum-revert-form"></form> <form name="gollum-revert" action="{{revert_path}}/{{escaped_url_path}}/{{before}}/{{after}}" method="post" id="gollum-revert-form"></form>
<span class="pb-4"> <span class="pb-4">
<button class="btn btn-sm" type="submit" onclick="$('#gollum-revert-form').submit()">Revert Changes</button> <button
</span> class="btn btn-sm"
{{/allow_editing}} onclick="$('#gollum-revert-form').submit()"
{{/show_revert}} type="submit"
<a href="{{history_path}}/{{escaped_url_path}}" class="btn btn-sm action-page-history">Back to Page History</a> >
</div> {{t.revert}}
</button>
</span>
{{/allow_editing}}
{{/show_revert}}
<div class="Box data highlight"> <a
<div class="Box-header Box--condensed Box-header--gray">{{path}} <span class="px-2 float-right">Comparing {{before}} to {{after}}</span></div> class="btn btn-sm action-page-history"
<table > href="{{history_path}}/{{escaped_url_path}}"
{{#lines}} >
<tr> {{t.back_to_page_history}}
<td class="line_numbers">{{ldln}}</td> </a>
<td class="line_numbers">{{rdln}}</td> </div>
<td>
<div class="{{class}} pl-2">{{line}}</div>
</td>
</tr>
{{/lines}}
</table>
</div>
</div>
<div class="pt-4" id="footer"> <div class="Box data highlight">
{{#show_revert}} <div class="Box-header Box--condensed Box-header--gray">
{{#allow_editing}} {{path}}
<span class="pt-4"><button class="btn btn-sm gollum-revert-button" type="submit" onclick="$('#gollum-revert-form').submit()">Revert Changes</button></span>
{{/allow_editing}} <span class="px-2 float-right">
{{/show_revert}} {{t.comparing_from}}
<div class="pt-4"> </span>
<a href="#">Back to Top</a> </div>
</div>
</div> <table>
{{#lines}}
<tr>
<td class="line_numbers">{{ldln}}</td>
<td class="line_numbers">{{rdln}}</td>
<td>
<div class="{{class}} pl-2">{{line}}</div>
</td>
</tr>
{{/lines}}
</table>
</div>
</div>
<div class="pt-4" id="footer">
{{#show_revert}}
{{#allow_editing}}
<span class="pt-4">
<button
class="btn btn-sm gollum-revert-button"
onclick="$('#gollum-revert-form').submit()"
type="submit"
>
{{t.revert}}
</button>
</span>
{{/allow_editing}}
{{/show_revert}}
<div class="pt-4">
<a href="#">
{{t.back_to_top}}
</a>
</div>
</div>
</div> </div>
+1 -1
View File
@@ -1,6 +1,6 @@
<div id="wiki-wrapper" class="error"> <div id="wiki-wrapper" class="error">
<div id="error"> <div id="error">
<h1>Error</h1> <h1>{{t.error}}</h1>
<p> <p>
{{message}} {{message}}
</p> </p>
+36 -27
View File
@@ -4,7 +4,7 @@
<h1 class="header-title text-center text-md-left py-4"> <h1 class="header-title text-center text-md-left py-4">
<span class="f1-light text-gray-light"> <span class="f1-light text-gray-light">
History for {{t.history_for}}
</span> </span>
{{name}} {{name}}
</h1> </h1>
@@ -14,32 +14,41 @@
{{>pagination}} {{>pagination}}
<form name="selection-form" id="selection-form" method="get" action="{{compare_path}}/{{escaped_url_path}}"></form> <form name="selection-form" id="selection-form" method="get" action="{{compare_path}}/{{escaped_url_path}}"></form>
<div id="page-history-list" class="Box Box--condensed flex-auto"> <div id="page-history-list" class="Box Box--condensed flex-auto">
<form id="version-form"> <form id="version-form">
<ul> <ul>
{{#versions}} {{#versions}}
<li class="Box-row border-top Box-row--hover-gray d-flex flex-items-center"> <li class="Box-row border-top Box-row--hover-gray d-flex flex-items-center">
<span class="pr-2"><input class="checkbox" type="checkbox" name="versions[]" value="{{id}}"></span> <span class="pr-2"><input class="checkbox" type="checkbox" name="versions[]" value="{{id}}"></span>
<span class="float-left col-2" id="user-icons">{{>author_template}}</span> <span class="float-left col-2" id="user-icons">{{>author_template}}</span>
<time class="flex-auto col-1 text-gray-light" datetime="{{datetime}}" data-format="{{date_format}}">{{date}}</time> <time class="flex-auto col-1 text-gray-light" datetime="{{datetime}}" data-format="{{date_format}}">{{date}}</time>
<span class="flex-auto col-5">{{message}}</span> <span class="flex-auto col-5">{{message}}</span>
<span class="pl-4 float-right"> <span class="pl-4 float-right">
<a href="{{href}}" class="btn btn-outline text-mono">{{id7}}</a> <a href="{{href}}" class="btn btn-outline text-mono">{{id7}}</a>
<a href="{{href_page}}" title="Browse the page at this point in the history" class="btn btn-outline">{{#octicon}}code{{/octicon}}</a> <a
</span> class="btn btn-outline"
</li> href="{{href_page}}"
{{/versions}} title="{{t.browse_in_history_description}}"
</ul> >
</form> {{#octicon}}code{{/octicon}}
</div> </a>
</span>
</li>
{{/versions}}
</ul>
</form>
</div>
<div id="footer">
<div class="pt-4">
<div id="footer"> <button
<div class="pt-4"> class="btn btn-sm action-compare-revision"
<button class="btn btn-sm action-compare-revision" type="submit">Compare Revisions</button> type="submit"
</div> >
</div> {{t.compare_revisions}}
</button>
</div>
</div>
</div> </div>
+17 -4
View File
@@ -21,8 +21,16 @@
<span class="pr-2">{{{icon}}}</span> <span class="pr-2">{{{icon}}}</span>
<span><a href="{{url}}">{{name}}</a></span> <span><a href="{{url}}">{{name}}</a></span>
{{#allow_editing}} {{#allow_editing}}
{{#is_file}}<button class="btn btn-sm float-right delete-file" data-file-path="{{file_path}}" data-confirm="Are you sure you want to delete {{name}}?">{{#octicon}}trash{{/octicon}}</button>{{/is_file}} {{#is_file}}
{{/allow_editing}} <button
class="btn btn-sm float-right delete-file"
data-confirm="{{t.delete_confirmation}}"
data-file-path="{{file_path}}"
>
{{#octicon}}trash{{/octicon}}
</button>
{{/is_file}}
{{/allow_editing}}
</li> </li>
{{/files_folders}} {{/files_folders}}
</ul> </ul>
@@ -33,13 +41,18 @@
{{#no_results}} {{#no_results}}
<p id="no-results"> <p id="no-results">
There are no pages in <strong>{{current_path}}</strong> on <strong>{{ref}}</strong>. {{t.no_pages_in}}
<strong>{{current_path}}</strong>
{{t.on}}
<strong>{{ref}}</strong>.
</p> </p>
{{/no_results}} {{/no_results}}
</div> </div>
<div class="pt-4" id="footer"> <div class="pt-4" id="footer">
<a href="#">Back to Top</a> <a href="#">
{{t.back_to_top}}
</a>
</div> </div>
</div> </div>
+20 -3
View File
@@ -1,6 +1,23 @@
<nav class="paginate-container" aria-label="Pagination"> <nav class="paginate-container" aria-label="{{tt.pagination.aria.label}}">
<div class="pagination" id="pagination"> <div class="pagination" id="pagination">
<a id="prev" href="?page_num={{previous_page}}{{query_string}}" class="previous_page {{^previous_page}}disabled{{/previous_page}}">Previous</span> <a
<a id="next" href="?page_num={{next_page}}{{query_string}}" class="next_page {{^next_page}}disabled{{/next_page}}" rel="next" aria-label="Next Page">Next</a> aria-label="{{td.pagination.aria.previous_page}}"
class="previous_page {{^previous_page}}disabled{{/previous_page}}"
href="?page_num={{previous_page}}{{query_string}}"
id="prev"
rel="prev"
>
{{tt.pagination.previous}}
</a>
<a
aria-label="{{td.pagination.aria.next_page}}"
class="next_page {{^next_page}}disabled{{/next_page}}"
href="?page_num={{next_page}}{{query_string}}"
id="next"
rel="next"
>
{{tt.pagination.next}}
</a>
</div> </div>
</nav> </nav>
+9 -5
View File
@@ -4,7 +4,7 @@
<h1 class="header-title text-center text-md-left py-4"> <h1 class="header-title text-center text-md-left py-4">
<span class="f1-light text-gray-light"> <span class="f1-light text-gray-light">
Search results for {{t.search_results_for}}
</span> </span>
{{name}} {{name}}
</h1> </h1>
@@ -21,7 +21,12 @@
<li class="Box-row Box-row--gray"> <li class="Box-row Box-row--gray">
<span class="Counter Counter--gray tooltipped tooltipped-w" aria-label="{{filename_count}} hits in filename - {{count}} hits in content">{{filename_count}} - {{count}}</span>&nbsp; <span class="Counter Counter--gray tooltipped tooltipped-w" aria-label="{{filename_count}} hits in filename - {{count}} hits in content">{{filename_count}} - {{count}}</span>&nbsp;
<span class="text-bold"><a href="{{href}}">{{name}}</a></span> <span class="text-bold"><a href="{{href}}">{{name}}</a></span>
<button class="btn-link tooltipped tooltipped-w float-right toggle-context" aria-label="Show all {{count}} hits in this page">{{#octicon}}search{{/octicon}}</button> <button
class="btn-link tooltipped tooltipped-w float-right toggle-context"
aria-label="{{t.aria.show_all}}"
>
{{#octicon}}search{{/octicon}}
</button>
</li> </li>
<div class="search-context"> <div class="search-context">
@@ -29,7 +34,6 @@
<li class="Box-row border-0"><span class="text-italic">{{.}}</span></li> <li class="Box-row border-0"><span class="text-italic">{{.}}</span></li>
{{/context}} {{/context}}
</div> </div>
{{/results}} {{/results}}
</ul> </ul>
</div> </div>
@@ -37,12 +41,12 @@
{{#no_results}} {{#no_results}}
<p id="no-results"> <p id="no-results">
There are no results for your search <strong>{{query}}</strong>. {{t.no_results}} <strong>{{query}}</strong>.
</p> </p>
{{/no_results}} {{/no_results}}
<div id="footer" class="mt-4"> <div id="footer" class="mt-4">
<a class="btn" href="#">Back to Top</a> <a class="btn" href="#">{{t.back_to_top}}</a>
</div> </div>
</div> </div>
+1 -1
View File
@@ -6,7 +6,7 @@ module Precious
attr_reader :page, :diff, :versions, :message, :allow_editing attr_reader :page, :diff, :versions, :message, :allow_editing
def title def title
"Comparison of #{@page.title}" [t[:comparison_of], @page.title].join(" ")
end end
def before def before
@@ -21,6 +21,13 @@ module Precious
autofill I18n.t(locale_klass_name) autofill I18n.t(locale_klass_name)
end end
# Returns all I18n translation strings from the root of an I18n YAML file.
# Otherwise, it works exactly like the `#t` method that's also defined in
# this file.
def tt
autofill I18n.t('.')
end
private private
# Recursively looks up I18n translation values and autofills any YAML # Recursively looks up I18n translation values and autofills any YAML
+1 -1
View File
@@ -9,7 +9,7 @@ module Precious
attr_reader :wiki attr_reader :wiki
def title def title
"Latest Changes (Globally)" t[:title]
end end
def versions def versions
+1 -1
View File
@@ -20,7 +20,7 @@ module Precious
end end
def title def title
"Home" t[:title]
end end
def has_path def has_path
+2 -2
View File
@@ -3,11 +3,11 @@ require 'pathname'
module Precious module Precious
module Views module Views
class Overview < Layout class Overview < Layout
attr_reader :results, :ref, :allow_editing, :newable attr_reader :name, :results, :ref, :allow_editing, :newable
HIDDEN_PATHS = ['.gitkeep'] HIDDEN_PATHS = ['.gitkeep']
def title def title
"Overview of #{@ref}" t[:title]
end end
# def editable # def editable
+1 -1
View File
@@ -23,7 +23,7 @@ module Precious
end end
def title def title
"Search results for " + @query t[:title]
end end
def search def search
+98 -2
View File
@@ -15,12 +15,13 @@ describe Precious::Views::LocaleHelpers do
end end
def setup def setup
::I18n.available_locales = [:en, :de] I18n.available_locales = [:en, :de]
::I18n.load_path = Dir[File.expand_path("test/support/locales" + "/*.yml")] I18n.load_path = Dir[File.expand_path("test/support/locales" + "/*.yml")]
end end
def teardown def teardown
I18n.locale = :en I18n.locale = :en
I18n.load_path = Dir[::File.expand_path("lib/gollum/locales") + "/*.yml"]
end end
let(:dummy_instance) { TestClass.new } let(:dummy_instance) { TestClass.new }
@@ -131,4 +132,99 @@ describe Precious::Views::LocaleHelpers do
end end
end end
end end
describe "#tt" do
describe "mustache usage" do
let(:subject) { dummy_instance.render(mustache_template) }
let(:mustache_template) { "{{ tt.test_class.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) { "{{ tt.test_class.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) { "{{ tt.test_class.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 "missing translations" do
let(:mustache_template) {
"{{ tt.test_class.nested.nonexistent_key }}"
}
it "outputs an empty string" do
_(subject).must_be_empty
end
end
end
describe "usage" do
let(:subject) { dummy_instance.tt }
it "returns a hash" do
_(subject).must_be_kind_of Hash
end
it "returns all present translation keys" do
i18n_keys = I18n.t(".").keys
_(subject.keys).must_equal i18n_keys
end
it "returns nested keys" do
nested_keys = subject[:test_class][:author_info].keys
_(nested_keys).must_equal [:full]
end
describe "auto-filled YAML arguments" do
let(:subject) { dummy_instance.tt[:test_class][: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 end