From 778f70d2d53f582cecb03f0ad3445a55ff5e2980 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Wed, 22 Apr 2015 10:49:40 -0500 Subject: [PATCH] i18n: Add language and spell check hints This patch adds Content-Language and <link rel="alternate"> information to the client interface for better integration with search engines. It also adds @lang attribute to the <html> element and various input, textarea, and richtext boxes for better spell check integration and accuracy. --- include/ajax.config.php | 4 +-- include/class.i18n.php | 9 ++++++ include/client/header.inc.php | 29 +++++++++++++++++-- include/staff/header.inc.php | 7 +++-- .../staff/templates/content-manage.tmpl.php | 6 ++-- include/staff/ticket-view.inc.php | 8 ++--- js/redactor-osticket.js | 6 ++++ scp/js/jquery.translatable.js | 3 ++ scp/js/scp.js | 10 +++++++ 9 files changed, 70 insertions(+), 12 deletions(-) diff --git a/include/ajax.config.php b/include/ajax.config.php index c263bd4a2..a4e11bf22 100644 --- a/include/ajax.config.php +++ b/include/ajax.config.php @@ -46,7 +46,7 @@ class ConfigAjaxAPI extends AjaxController { 'has_rtl' => $rtl, 'lang_flag' => strtolower($info['flag'] ?: $locale ?: $sl), 'primary_lang_flag' => strtolower($primary_info['flag'] ?: $primary_locale ?: $primary_sl), - 'primary_language' => $primary, + 'primary_language' => Internationalization::rfc1766($primary), 'secondary_languages' => $cfg->getSecondaryLanguages(), 'page_size' => $thisstaff->getPageLimit(), ); @@ -70,7 +70,7 @@ class ConfigAjaxAPI extends AjaxController { 'lang' => $lang, 'short_lang' => $sl, 'has_rtl' => $rtl, - 'primary_language' => $cfg->getPrimaryLanguage(), + 'primary_language' => Internationalization::rfc1766($cfg->getPrimaryLanguage()), 'secondary_languages' => $cfg->getSecondaryLanguages(), ); diff --git a/include/class.i18n.php b/include/class.i18n.php index 94f6b679c..27486ba9b 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -408,6 +408,15 @@ class Internationalization { return $locale; } + static function rfc1766($what) { + if (is_array($what)) + return array_map(array(get_called_class(), 'rfc1766'), $what); + + $lr = explode('_', $what); + if (isset($lr[1])) + $lr[1] = strtoupper($lr[1]); + return implode('-', $lr); + } static function getTtfFonts() { if (!class_exists('Phar')) diff --git a/include/client/header.inc.php b/include/client/header.inc.php index db6e4006c..c49e89639 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -8,11 +8,17 @@ $signout_url = ROOT_PATH . "logout.php?auth=".$ost->getLinkToken(); header("Content-Type: text/html; charset=UTF-8"); ?> <!DOCTYPE html> -<html <?php +<html<?php if (($lang = Internationalization::getCurrentLanguage()) && ($info = Internationalization::getLanguageInfo($lang)) && (@$info['direction'] == 'rtl')) - echo 'dir="rtl" class="rtl"'; + echo ' dir="rtl" class="rtl"'; +if ($lang) { + $langs = array_unique(array($lang, $cfg->getPrimaryLanguage())); + $langs = Internationalization::rfc1766($langs); + echo ' lang="' . $lang . '"'; + header("Content-Language: ".implode(', ', $langs)); +} ?>> <head> <meta charset="utf-8"> @@ -48,6 +54,25 @@ if (($lang = Internationalization::getCurrentLanguage()) if($ost && ($headers=$ost->getExtraHeaders())) { echo "\n\t".implode("\n\t", $headers)."\n"; } + + // Offer alternate links for search engines + // @see https://support.google.com/webmasters/answer/189077?hl=en + if (($all_langs = Internationalization::getConfiguredSystemLanguages()) + && (count($all_langs) > 1) + ) { + $langs = Internationalization::rfc1766(array_keys($all_langs)); + $qs = array(); + parse_str($_SERVER['QUERY_STRING'], $qs); + foreach ($langs as $L) { + $qs['lang'] = $L; ?> + <link rel="alternate" href="//<?php echo $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>?<?php + echo http_build_query($qs); ?>" hreflang="<?php echo $L; ?>" /> +<?php + } ?> + <link rel="alternate" href="//<?php echo $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>" + hreflang="x-default"; +<?php + } ?> </head> <body> diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index 0945a9164..0e7bf469c 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -2,11 +2,14 @@ header("Content-Type: text/html; charset=UTF-8"); if (!isset($_SERVER['HTTP_X_PJAX'])) { ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html <?php +<html<?php if (($lang = Internationalization::getCurrentLanguage()) && ($info = Internationalization::getLanguageInfo($lang)) && (@$info['direction'] == 'rtl')) - echo 'dir="rtl" class="rtl"'; + echo ' dir="rtl" class="rtl"'; +if ($lang) { + echo ' lang="' . Internationalization::rfc1766($lang) . '"'; +} ?>> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> diff --git a/include/staff/templates/content-manage.tmpl.php b/include/staff/templates/content-manage.tmpl.php index 3fa4e8fcb..e90b8ff87 100644 --- a/include/staff/templates/content-manage.tmpl.php +++ b/include/staff/templates/content-manage.tmpl.php @@ -27,7 +27,8 @@ if (count($langs) > 1) { ?> class="tab_content left-tabs" style="padding:0" lang="<?php echo $cfg->getPrimaryLanguage(); ?>"> <div class="error"><?php echo $errors['name']; ?></div> <input type="text" style="width: 100%; font-size: 14pt" name="name" value="<?php - echo Format::htmlchars($info['title']); ?>" /> + echo Format::htmlchars($info['title']); ?>" spellcheck="true" + lang="<?php echo $cfg->getPrimaryLanguage(); ?>" /> <div style="margin-top: 5px"> <div class="error"><?php echo $errors['body']; ?></div> <textarea class="richtext no-bar" name="body" @@ -46,7 +47,8 @@ if (count($langs) > 1) { ?> <input type="text" style="width: 100%; font-size: 14pt" name="trans[<?php echo $tag; ?>][title]" value="<?php echo Format::htmlchars($trans['title']); ?>" - placeholder="<?php echo __('Title'); ?>" /> + placeholder="<?php echo __('Title'); ?>" spellcheck="true" + lang="<?php echo $tag; ?>" /> <div style="margin-top: 5px"> <textarea class="richtext no-bar" data-direction=<?php echo $nfo['direction']; ?> data-root-context="<?php echo $content->getType(); ?>" diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 35fb34aca..1bd831a38 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -433,7 +433,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); </ul> <?php if ($role->hasPerm(TicketModel::PERM_REPLY)) { ?> - <form id="reply" class="tab_content" action="tickets.php?id=<?php + <form id="reply" class="tab_content spellcheck" action="tickets.php?id=<?php echo $ticket->getId(); ?>" name="reply" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> @@ -622,7 +622,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); </form> <?php } ?> - <form id="note" class="hidden tab_content" action="tickets.php?id=<?php + <form id="note" class="hidden tab_content spellcheck" action="tickets.php?id=<?php echo $ticket->getId(); ?>#note" name="note" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> @@ -705,7 +705,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); </form> <?php if ($role->hasPerm(TicketModel::PERM_TRANSFER)) { ?> - <form id="transfer" class="hidden tab_content" action="tickets.php?id=<?php + <form id="transfer" class="hidden tab_content spellcheck" action="tickets.php?id=<?php echo $ticket->getId(); ?>#transfer" name="transfer" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="ticket_id" value="<?php echo $ticket->getId(); ?>"> @@ -766,7 +766,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); } ?> <?php if ($role->hasPerm(TicketModel::PERM_ASSIGN)) { ?> - <form id="assign" class="hidden tab_content" action="tickets.php?id=<?php + <form id="assign" class="hidden tab_content spellcheck" action="tickets.php?id=<?php echo $ticket->getId(); ?>#assign" name="assign" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js index 23b1ee76a..19ff52a6a 100644 --- a/js/redactor-osticket.js +++ b/js/redactor-osticket.js @@ -268,6 +268,12 @@ $(function() { 'tabFocus': false, 'toolbarFixedBox': true, 'focusCallback': function() { this.$box.addClass('no-pjax'); }, + 'initCallback': function() { + this.$editor.attr('spellcheck', 'true'); + var lang = this.$editor.closest('[lang]').attr('lang'); + if (lang) + this.$editor.attr('lang', lang); + }, 'linkSize': 100000, 'definedLinks': 'ajax.php/config/links' }, options||{}); diff --git a/scp/js/jquery.translatable.js b/scp/js/jquery.translatable.js index 08a28ae3c..08f754a1a 100644 --- a/scp/js/jquery.translatable.js +++ b/scp/js/jquery.translatable.js @@ -60,6 +60,7 @@ .focus($.proxy(function() { this.addClass('focus'); }, this.$container)) .blur($.proxy(function() { this.removeClass('focus'); }, this.$container)); getConfig().then($.proxy(function(c) { + this.attr({'spellcheck': 'true', 'lang': c.primary_language}) $('<span class="flag"></span>') .addClass('flag-' + c.primary_lang_flag) .insertAfter(this); @@ -113,6 +114,8 @@ .text(info.name) .prepend($('<span>').addClass('flag flag-'+info.flag)) .append($('<input type="text" data-lang="'+lang+'">') + .attr('lang', lang) + .attr('spellcheck', 'true') .attr('dir', info.direction || 'ltr') .on('change keydown', $.proxy(this.showCommit, this)) .val(text) diff --git a/scp/js/scp.js b/scp/js/scp.js index 24be12c76..502b7f061 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -563,6 +563,16 @@ $(document).keydown(function(e) { } }); + +$(document).on('focus', 'form.spellcheck textarea, form.spellcheck input[type=text]', function() { + var $this = $(this); + if ($this.attr('lang') !== undefined) + return; + var lang = $(this).closest('[lang]').attr('lang'); + if (lang) + $(this).attr({'spellcheck':'true', 'lang': lang}); +}); + $.toggleOverlay = function (show) { if (typeof(show) === 'undefined') { return $.toggleOverlay(!$('#overlay').is(':visible')); -- GitLab