Bug 906943 - Show the translation bar at the top of the page if it's localized into visitor's language

This commit is contained in:
Kohei Yoshino 2013-12-10 21:54:58 -05:00
Родитель ebbe5d57bf
Коммит b80d456614
17 изменённых файлов: 316 добавлений и 16 удалений

Просмотреть файл

@ -64,7 +64,7 @@
{% block site_header %}
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}
<span class="toggle" role="button" aria-controls="nav-main-menu" tabindex="0">{{_('Menu')}}</span>
<nav id="nav-main" role="navigation">

Просмотреть файл

@ -59,7 +59,7 @@
{% block site_header %}
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}
<nav id="nav-main" role="navigation">

Просмотреть файл

@ -26,7 +26,7 @@
<header id="masthead">
<div class="wrapper stuck">
<div class="inner">
<a href="{{ url('mozorg.home') }}" id="tabzilla">Mozilla</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_logo %}
<h2 id="ffos-main-logo"><a href="#masthead" class="nav"><img src="{{ MEDIA_URL }}img/firefox/os/logo/firefox-os.png" alt="Firefox OS"></a></h2>

Просмотреть файл

@ -37,7 +37,7 @@
{% block site_header %}
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}
<span class="toggle" role="button" aria-controls="nav-main-menu" tabindex="0">{{_('Menu')}}</span>
<nav id="nav-main" role="navigation">

Просмотреть файл

@ -22,7 +22,7 @@
{% block site_header %}
<header id="masthead">
<a href="//www.mozilla.org/" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}{% endblock %}

Просмотреть файл

@ -22,7 +22,7 @@
{% block site_header %}
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">Mozilla</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}{% endblock %}

Просмотреть файл

@ -14,7 +14,7 @@
{% block site_header %}
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
{% block site_header_nav %}{% endblock %}
</header>
{% endblock %}

Просмотреть файл

@ -643,6 +643,8 @@ DOMAIN_METHODS = {
'tower.extract_tower_template'),
('%s/**/templates/**.js' % PROJECT_MODULE,
'tower.extract_tower_template'),
('%s/**/templates/**.jsonp' % PROJECT_MODULE,
'tower.extract_tower_template'),
],
}
@ -885,3 +887,5 @@ DONATE_LOCALE_LINK = {
# Mapbox token for spaces and communities pages
MAPBOX_TOKEN = 'examples.map-9ijuk24y'
TABZILLA_INFOBAR_OPTIONS = 'translation'

Просмотреть файл

@ -27,7 +27,7 @@
{% block content %}
<div id="content">
<header id="masthead">
<a href="{{ url('mozorg.home') }}" id="tabzilla">{{ _('Mozilla') }}</a>
<a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="{{ settings.TABZILLA_INFOBAR_OPTIONS }}">{{ _('Mozilla') }}</a>
</header>
{% block sidebar %}

Просмотреть файл

@ -63,6 +63,8 @@
<code>&lt;link href="//mozorg.cdn.mozilla.net/media/css/tabzilla-min.css" rel="stylesheet" /&gt;</code></li>
<li>Include the tabzilla.js file in your template (preferably in the footer) <br />
<code>&lt;script src="//mozorg.cdn.mozilla.net/tabzilla/tabzilla.js"&gt;&lt;/script&gt;</code></li>
<li>If you'd like to have the Translation Bar extension, include <a href="https://support.google.com/webmasters/answer/189077">alternate URLs</a> in the <code>&lt;head&gt;</code> element and add the <code>data-infobar</code> attribute to the tab link, with the <code>translation</code> option <br />
<code>&lt;a href="{{ url('mozorg.home') }}" id="tabzilla" data-infobar="translation"&gt;{{ _('Mozilla') }}&lt;/a&gt;</code></li>
</ol>
<p>See the <a href="http://bedrock.readthedocs.org/en/latest/tabzilla.html">Tabzilla documentation</a> for full instructions and notes on localization.</p>
</section>

Просмотреть файл

@ -64,6 +64,7 @@
* @author Michael Gauthier <mike@silverorange.com>
* @author Steven Garrity <steven@silverorange.com>
* @author Isac Lagerblad <icaaaq@gmail.com>
* @author Kohei Yoshino <kohei.yoshino@gmail.com>
*/
var Tabzilla = (function (Tabzilla) {
@ -240,6 +241,180 @@ var Tabzilla = (function (Tabzilla) {
}
return 0;
};
var Infobar = function (id, name) {
this.id = id;
this.name = name;
this.disabled = false;
this.prefName = 'tabzilla.infobar.' + id + '.disabled';
// Read the preference
try {
if (sessionStorage.getItem(this.prefName) === 'true') {
this.disabled = true;
}
} catch (ex) {}
// If there is already another infobar, don't show this
if ($('#tabzilla-infobar').length) {
this.disabled = true;
}
};
Infobar.prototype.show = function (str) {
// A infobar can be disabled by pref.
// And check the existence of another infobar again
if (this.disabled || $('#tabzilla-infobar').length) {
return;
}
var self = this;
var bar = self.element = $(
'<div id="tabzilla-infobar" class="' + self.id + '" role="dialog"><div>'
+ '<p>' + str.message + '</p><ul>'
+ '<li><a href="#" class="btn-accept" role="button">' + str.accept + '</a></li>'
+ '<li><a href="#" class="btn-cancel" role="button">' + str.cancel + '</a></li>'
+ '</ul></div></div>').prependTo(panel);
bar.find('.btn-accept').click(function (event) {
event.preventDefault();
self.trackEvent(self.onaccept.trackAction || 'accept',
self.onaccept.trackLabel,
self.onaccept.callback);
self.hide();
});
bar.find('.btn-cancel').click(function (event) {
event.preventDefault();
self.trackEvent(self.oncancel.trackAction || 'cancel',
self.oncancel.trackLabel,
self.oncancel.callback);
self.hide();
try {
sessionStorage.setItem(self.prefName, 'true');
} catch (ex) {}
});
panel.trigger('infobar-showing');
self.trackEvent(self.onshow.trackAction || 'show',
self.onshow.trackLabel,
self.onshow.callback);
if (opened) {
bar.css('height', 0)
.animate({'height': bar.find('div').outerHeight()}, 200,
function () { panel.trigger('infobar-shown'); });
} else {
panel.animate({'height': bar.height()}, 200,
function () { panel.trigger('infobar-shown'); });
}
return bar;
};
Infobar.prototype.hide = function () {
var self = this;
var target = (opened) ? self.element : panel;
panel.trigger('infobar-hiding');
target.animate({'height': 0}, 200, function () {
self.element.remove();
panel.trigger('infobar-hidden');
});
};
Infobar.prototype.trackEvent = function (action, label, callback) {
if (typeof(_gaq) !== 'object') {
return;
}
// Track events with Google Analytics
window._gaq.push(['_trackEvent',
'Tabzilla - ' + this.name, action, label]);
if (callback) {
var timer = null;
var _callback = function () {
clearTimeout(timer);
callback();
};
timer = setTimeout(_callback, 500);
window._gaq.push(_callback);
}
};
Infobar.prototype.onshow = {};
Infobar.prototype.onaccept = {};
Infobar.prototype.oncancel = {};
var setupTransbar = function () {
var transbar = new Infobar('transbar', 'Translation Bar');
if (transbar.disabled) {
return;
}
// Compare the user's language and the page's language
var userLang = navigator.language || navigator.browserLanguage;
var pageLang = document.documentElement.lang;
if (!userLang || !pageLang ||
userLang.toLowerCase() === pageLang.toLowerCase()) {
return;
}
// Normalize the user language in the form of ab or ab-CD
userLang = userLang.replace(/^(\w+)(?:-(\w+))?$/, function (m, p1, p2) {
return p1.toLowerCase() + ((p2) ? '-' + p2.toUpperCase() : '');
});
// Check the availability of the translated page for the user.
// Use an alternate URL in <head> or a language option in <form>
var langLink = $(['link[hreflang="' + userLang + '"]',
// The user language can be ab-CD while the page language is ab
// (Example: fr-FR vs fr, ja-JP vs ja)
'link[hreflang="' + userLang.split('-')[0] + '"]'].join(','));
var langOption = $(['#language [value="' + userLang + '"]',
// Languages in the language switcher are uncapitalized on some
// sites (AMO, Firefox Flicks)
'#language [value="' + userLang.toLowerCase() + '"]',
// The user language can be ab-CD while the page language is ab
// (Example: fr-FR vs fr, ja-JP vs ja)
'#language [value="' + userLang.split('-')[0] + '"]',
// Sometimes the value of a language switcher option is the path of
// a localized page on some sites (MDN)
'#language [value^="/' + userLang + '/"]'].join(','));
if (!langLink.length && !langOption.length) {
return;
}
// Normalize the user language again, based on the language of the site
userLang = (langLink.length) ? langLink.attr('hreflang')
: langOption.val();
// The language label: English (US) -> English
var langLabel = (langLink.length) ? langLink.attr('title')
: langOption.text();
langLabel = langLabel.match(/^(.+?)(\s\(.+\))?$/)[1];
// Log the language of the current page
transbar.onshow.trackLabel = transbar.oncancel.trackLabel = userLang;
transbar.oncancel.trackAction = 'hide';
// If the user selects Yes, show the translated page
transbar.onaccept = {
trackAction: 'change',
trackLabel: userLang,
callback: function () {
if (langLink.length) {
location.href = langLink.attr('href');
} else {
langOption.attr('selected', 'selected').get(0).form.submit();
}
}
};
// Fetch the localized strings and show the Translation Bar
$.ajax({ url: '{{ settings.CDN_BASE_URL }}/' + userLang + '/tabzilla/transbar.jsonp',
cache: false, crossDomain: true, dataType: 'jsonp',
jsonpCallback: "_", success: function (str) {
str.message = str.message.replace('%s', langLabel);
transbar.show(str).attr('lang', userLang);
}});
};
var setupGATracking = function () {
// track tabzilla links in GA
$('#tabzilla-contents').on('click', 'a', function (e) {
@ -356,6 +531,17 @@ var Tabzilla = (function (Tabzilla) {
}
});
// Information Bars in order of priority
var infobars = {
translation: setupTransbar
};
$.each((tab.data('infobar') || '').split(' '), function (index, value) {
var setup = infobars[value];
if (setup) {
setup.call();
}
});
setupGATracking();
// Careers teaser in error console.

Просмотреть файл

@ -0,0 +1,9 @@
{% set_lang_files "tabzilla/tabzilla" %}
_({
{# L10n: %s will be replaced with a language name like English. You can instead hardcode your language name in the string if you want. -#}
"message": "{{ _('Would you like to see this page in %s?')|js_escape }}",
{# L10n: Your translation can just say Yes -#}
"accept": "{{ _('Yes, please!')|js_escape }}",
{# L10n: Your translation can just say No -#}
"cancel": "{{ _('No, thanks.')|js_escape }}"
})

Просмотреть файл

@ -9,4 +9,5 @@ import views
urlpatterns = patterns('',
url(r'^tabzilla\.js$', views.tabzilla_js, name='tabzilla'),
url(r'^transbar\.jsonp$', views.transbar_jsonp, name='transbar'),
)

Просмотреть файл

@ -10,10 +10,8 @@ from lib import l10n_utils
from bedrock.mozorg.decorators import cache_control_expires
@cache_control_expires(12)
def tabzilla_js(request):
resp = l10n_utils.render(request, 'tabzilla/tabzilla.js',
content_type='text/javascript')
def _resp(request, path, ctype):
resp = l10n_utils.render(request, path, content_type=ctype)
is_enabled = not settings.TEMPLATE_DEBUG and settings.CDN_BASE_URL
if is_enabled and isinstance(resp, HttpResponseRedirect):
@ -23,3 +21,15 @@ def tabzilla_js(request):
cdn_base = protocol + settings.CDN_BASE_URL
resp['location'] = cdn_base + resp['location']
return resp
@cache_control_expires(12)
def tabzilla_js(request):
return _resp(request, 'tabzilla/tabzilla.js', 'text/javascript')
@cache_control_expires(12)
def transbar_jsonp(request):
resp = _resp(request, 'tabzilla/transbar.jsonp', 'application/javascript')
resp['Access-Control-Allow-Origin'] = '*'
return resp

Просмотреть файл

@ -57,3 +57,26 @@ As the universal tab does inject HTML/CSS into the DOM, some there are some requ
Any background image or absolutely positioned element attached to the ``body`` element would not move with the rest of the contents when the tab slides open. Instead, any such background or element should be attached to anoter HTML element in the page (a wrapper div, for example). Note that this issue does not apply to solid background colors, or backgrounds that do not vary vertically (solid vertical stripes, for example).
If jQuery is already included on the page, it will be used by Tabzilla. If jQuery is not already on the page, it will automatically be included after the page has loaded.
Translation Bar
---------------
Tabzilla has an opt-in extension called *Translation Bar* that automatically offers a link to a localized page, if available, based on the user's locale. It is intended to improve international user experience.
Adding the Translation Bar extension to Tabzilla requires:
1. Include `alternate URLs <https://support.google.com/webmasters/answer/189077>`_ in the ``<head>`` element. For example::
<link rel="alternate" hreflang="en-US" href="http://www.mozilla.org/en-US/firefox/new/" title="English (US)">
<link rel="alternate" hreflang="fr" href="http://www.mozilla.org/fr/firefox/new/" title="Français">
The Translation Bar alternatively detects available translations by looking for a language switcher like below, but implementation of alternate URLs is recommended also from the SEO perspective::
<select id="language"><option value="en-US">English (US)</option></select>
2. Add the ``data-infobar`` attribute to the tab link, with the ``translation`` option::
<a href="https://www.mozilla.org/" id="tabzilla" data-infobar="translation">mozilla</a>
.. note:: Though the Translation Bar is currently implemented as an extension of Tabzilla, it might be moved to a standalone language utility in the future.

Просмотреть файл

@ -312,6 +312,36 @@
#tabzilla-panel #tabzilla-nav ul li#tabzilla-search input::-webkit-input-placeholder { color: #ddd; }
#tabzilla-panel #tabzilla-nav ul li#tabzilla-search input:-moz-placeholder { color: #ddd; }
#tabzilla-infobar {
position: relative;
overflow: hidden;
margin: 0 auto;
width: 940px;
font-size: 12px;
line-height: 1.5;
div {
padding: 5px 0;
}
p {
display: inline;
margin: 0 10px 0 0;
}
ul {
display: inline;
margin: 0;
}
li {
display: inline;
margin: 0 10px 0 0;
white-space: nowrap;
list-style-type: none;
}
}
// Firefox Flicks Promo
#tabzilla-promo .snippet {
@ -486,6 +516,10 @@ html[lang='de'] #tabzilla-promo .snippet#tabzilla-promo-donate-201312 a {
margin-bottom: 40px;
}
#tabzilla-infobar {
position: static;
width: 90%;
}
}
@media only screen and (max-width: 719px) {
@ -604,4 +638,12 @@ html[lang='de'] #tabzilla-promo .snippet#tabzilla-promo-donate-201312 a {
box-sizing: border-box;
}
#tabzilla-infobar {
position: static;
width: auto;
div {
padding: 10px 20px;
}
}
}

Просмотреть файл

@ -314,14 +314,30 @@
// toggle sticky masthead when tabzilla is opened/closed
$tabzilla.on('click', function () {
var $tab = $('#tabzilla-panel');
$('#masthead .wrapper').toggleClass('stuck');
var $wrapper = $('#masthead .wrapper');
//if we're on mobile then masthead position is relative
if ($w.width() < 760) { return; }
if ($tab.hasClass('open')) {
$tab.css('height', 0);
$wrapper.addClass('stuck');
} else {
$wrapper.removeClass('stuck');
}
});
// Support for an information bar of Tabzilla
$(document).on('infobar-showing', '#tabzilla-panel', function () {
var $wrapper = $('#masthead .wrapper');
if ($wrapper.hasClass('scroll')) {
$(this).stop(true); // Do not show the bar when scrolled down
} else {
$wrapper.removeClass('stuck');
}
}).on('infobar-hidden', '#tabzilla-panel', function () {
if (!$(this).hasClass('open')) {
$('#masthead .wrapper').addClass('stuck');
}
});
@ -340,14 +356,14 @@
if (direction === 'down') {
// if tabzilla is open then close if we start to scroll
if ($tab.hasClass('open')) {
$tab.css('height', 0);
$tabzilla.trigger('click');
}
$tabzilla.fadeOut('fast');
$btn.fadeIn();
$wrapper.animate({backgroundColor: '#fff'}, 'fast');
$current.promise().done(function () {
$wrapper.addClass('scroll');
$tab.css('height', 0);
$wrapper.addClass('scroll stuck');
});
$('#masthead ul li a').animate({color: '#0096DD'}, 'fast');
@ -359,6 +375,13 @@
$wrapper.removeClass('scroll');
});
$('#masthead ul li a').animate({color: '##484848'}, 'fast');
// Support for an information bar of Tabzilla
var $infobar = $('#tabzilla-infobar').css('height', 'auto');
if ($infobar.length) {
$tab.animate({'height': $infobar.height()}, 200, function () {
$(this).trigger('infobar-shown');
}).trigger('infobar-showing');
}
}
}, { offset: -1 });