diff --git a/Gemfile b/Gemfile
index 49e172ee1..af8bf1163 100644
--- a/Gemfile
+++ b/Gemfile
@@ -47,6 +47,7 @@ gem "honeybadger", "~> 4.9" # Used for tracking application errors
gem "honeycomb-beeline", "~> 2.7.0" # Monitoring and Observability gem
gem "html_truncator", "~> 0.4" # Truncate an HTML string properly
gem "htmlentities", "~> 4.3", ">= 4.3.4" # A module for encoding and decoding (X)HTML entities
+gem "i18n-js", "~> 3.9.0" # Helps with internationalization in Rails.
gem "httparty", "~> 0.19" # Makes http fun! Also, makes consuming restful web services dead easy
gem "imgproxy", "~> 2.0" # A gem that easily generates imgproxy URLs for your images
gem "inline_svg", "~> 1.7" # Embed SVG documents in your Rails views and style them with CSS
@@ -122,9 +123,11 @@ group :development do
gem "brakeman", "~> 5.1", require: false # Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis
gem "bundler-audit", "~> 0.9" # bundler-audit provides patch-level verification for Bundled apps
gem "derailed_benchmarks", "~> 2.1", require: false # A series of things you can use to benchmark a Rails or Ruby app
+ gem "easy_translate", "~> 0.5.1" # Google translate tie-in to be used with i18n tasks
gem "erb_lint", "~> 0.0.37", require: false # ERB Linter tool
gem "guard", "~> 2.18", require: false # Guard is a command line tool to easily handle events on file system modifications
gem "guard-livereload", "~> 2.5", require: false # Guard::LiveReload automatically reloads your browser when 'view' files are modified
+ gem "i18n-tasks", "~> 0.9.34" # Helpers to find and manage missing and unused translations
gem "listen", "~> 3.7", require: false # Helps 'listen' to file system modifications events (also used by other gems like guard)
gem "memory_profiler", "~> 1.0", require: false # Memory profiling routines for Ruby 2.3+
gem "web-console", "~> 4.1" # Rails Console on the Browser
diff --git a/Gemfile.lock b/Gemfile.lock
index e4c125f97..ff7359890 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -218,6 +218,9 @@ GEM
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
+ easy_translate (0.5.1)
+ thread
+ thread_safe
em-websocket (0.5.2)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
@@ -347,6 +350,7 @@ GEM
hashie (4.1.0)
heapy (0.2.0)
thor
+ highline (2.0.3)
hkdf (0.3.0)
honeybadger (4.9.0)
honeycomb-beeline (2.7.0)
@@ -376,6 +380,18 @@ GEM
activerecord (>= 5)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
+ i18n-js (3.9.0)
+ i18n (>= 0.6.6)
+ i18n-tasks (0.9.34)
+ activesupport (>= 4.0.2)
+ ast (>= 2.1.0)
+ erubi
+ highline (>= 2.0.0)
+ i18n
+ parser (>= 2.2.3.0)
+ rails-i18n
+ rainbow (>= 2.2.2, < 4.0)
+ terminal-table (>= 1.5.1)
ice_nine (0.11.2)
image_processing (1.12.1)
mini_magick (>= 4.9.5, < 5)
@@ -569,6 +585,9 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
+ rails-i18n (6.0.0)
+ i18n (>= 0.7, < 2)
+ railties (>= 6.0.0, < 7)
railties (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
@@ -769,8 +788,11 @@ GEM
stripe (> 5, < 6)
strong_migrations (0.7.8)
activerecord (>= 5)
+ terminal-table (3.0.1)
+ unicode-display_width (>= 1.1.1, < 3)
test-prof (1.0.7)
thor (1.1.0)
+ thread (0.2.2)
thread_safe (0.3.6)
tilt (2.0.10)
timecop (0.9.4)
@@ -878,6 +900,7 @@ DEPENDENCIES
dogstatsd-ruby (~> 4.8)
doorkeeper (~> 5.5)
dotenv-rails (~> 2.7.6)
+ easy_translate (~> 0.5.1)
email_validator (~> 2.2)
emoji_regex (~> 3.2)
erb_lint (~> 0.0.37)
@@ -906,6 +929,8 @@ DEPENDENCIES
htmlentities (~> 4.3, >= 4.3.4)
httparty (~> 0.19)
hypershield (~> 0.2.2)
+ i18n-js (~> 3.9.0)
+ i18n-tasks (~> 0.9.34)
imgproxy (~> 2.0)
inline_svg (~> 1.7)
jbuilder (~> 2.11)
diff --git a/app/assets/javascripts/base.js.erb b/app/assets/javascripts/base.js.erb
index a4a56931e..96e06ba51 100644
--- a/app/assets/javascripts/base.js.erb
+++ b/app/assets/javascripts/base.js.erb
@@ -4,8 +4,12 @@
//= require initializePage
//= require utilities/getImageForLink
//= require honeybadger-js/dist/honeybadger.js
+//= require i18n
//= require ahoy.js/dist/ahoy.js
+I18n.defaultLocale = 'en';
+I18n.locale = document.body.dataset.locale;
+I18n.translations = JSON.parse(document.getElementById('i18n-translations').dataset.translations);
var instantClick
, InstantClick = instantClick = function(document, location, $userAgent) {
// Internal variables
diff --git a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb
index 1024dec15..5f4295778 100644
--- a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb
+++ b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb
@@ -54,9 +54,9 @@ function initializeCommentsPage() {
if (publicReactionCounts[i].count > 0) {
if (publicReactionCounts[i].count > 1) {
- reactionsLabelWrapper.innerHTML = " likes";
+ reactionsLabelWrapper.innerHTML = ` ${I18n.t('core.like').toLowerCase()}s`;
} else {
- reactionsLabelWrapper.innerHTML = " like";
+ reactionsLabelWrapper.innerHTML = ` ${I18n.t('core.like').toLowerCase()}`;
}
reactionsCountWrapper.id = 'reactions-count-' + publicReactionCounts[i].id;
diff --git a/app/assets/javascripts/utilities/buildCommentHTML.js.erb b/app/assets/javascripts/utilities/buildCommentHTML.js.erb
index 0619469a3..3e9aba1c5 100644
--- a/app/assets/javascripts/utilities/buildCommentHTML.js.erb
+++ b/app/assets/javascripts/utilities/buildCommentHTML.js.erb
@@ -68,9 +68,9 @@ function buildCommentHTML(comment) {
@@ -471,7 +471,7 @@ Object {
- Add comment
+ [missing "en.core.add_comment" translation]
diff --git a/app/javascript/articles/components/CommentsCount.jsx b/app/javascript/articles/components/CommentsCount.jsx
index 967dc2003..641b1f539 100644
--- a/app/javascript/articles/components/CommentsCount.jsx
+++ b/app/javascript/articles/components/CommentsCount.jsx
@@ -1,6 +1,8 @@
import { h } from 'preact';
import PropTypes from 'prop-types';
import { Button } from '../../crayons/Button';
+import { locale } from '../../utilities/locale';
+
export const CommentsCount = ({ count, articlePath }) => {
const commentsSVG = () => (
@@ -28,7 +30,7 @@ export const CommentsCount = ({ count, articlePath }) => {
{count}
- {`${count > 1 ? 'comments' : 'comment'}`}
+ {`${count > 1 ? `${locale('core.comment')}s` : locale('core.comment') }`}
@@ -46,7 +48,7 @@ export const CommentsCount = ({ count, articlePath }) => {
data-testid="add-a-comment"
>
0
- Add comment
+ {locale('core.add_comment')}
);
}
diff --git a/app/javascript/articles/components/ReactionsCount.jsx b/app/javascript/articles/components/ReactionsCount.jsx
index fb0c3dbe0..6da4c139e 100644
--- a/app/javascript/articles/components/ReactionsCount.jsx
+++ b/app/javascript/articles/components/ReactionsCount.jsx
@@ -1,6 +1,7 @@
import { h } from 'preact';
import { articlePropTypes } from '../../common-prop-types';
import { Button } from '../../crayons/Button';
+import { locale } from '../../utilities/locale';
export const ReactionsCount = ({ article }) => {
const totalReactions = article.public_reactions_count || 0;
@@ -32,7 +33,7 @@ export const ReactionsCount = ({ article }) => {
{totalReactions}
- {`${totalReactions == 1 ? 'reaction' : 'reactions'}`}
+ {`${totalReactions == 1 ? locale('core.reaction') : `${locale('core.reaction')}s`}`}
diff --git a/app/javascript/listings/singleListing/DropdownMenu.jsx b/app/javascript/listings/singleListing/DropdownMenu.jsx
index 7fe9ca0e4..60a9b60f2 100644
--- a/app/javascript/listings/singleListing/DropdownMenu.jsx
+++ b/app/javascript/listings/singleListing/DropdownMenu.jsx
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import { h, Component, createRef } from 'preact';
import { listingPropTypes } from './listingPropTypes';
import { Button, Dropdown } from '@crayons';
+import { locale } from '@utilities/locale';
const Icon = () => (
-- Copy link
+ - ${ I18n.t('core.copy_link') }
- Settings
- - Report abuse
+ - ${ I18n.t('core.report_abuse') }
- Edit
- Delete
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 52aff4f66..87156d35f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception, prepend: true before_action :remember_cookie_sync before_action :forward_to_app_config_domain + before_action :determine_locale include SessionCurrentUser include ValidRequest @@ -178,6 +179,14 @@ class ApplicationController < ActionController::Base Stripe.log_level = Stripe::LEVEL_INFO end + def determine_locale + I18n.locale = if %w[en fr].include?(params[:locale]) + params[:locale] + else + Settings::UserExperience.default_locale + end + end + def remember_cookie_sync # Set remember cookie token in case not properly set. if user_signed_in? && diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bc0423dbe..df9fb72b0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -132,7 +132,7 @@ module ApplicationHelper followable_name = followable.name tag.button( - "Follow", + I18n.t("core.follow"), name: :button, type: :button, data: { diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 5e2a5ec96..83bbb406c 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -53,11 +53,11 @@ module CommentsHelper def like_button_text(comment) case comment.public_reactions_count when 0 - "Like" + I18n.t("core.like") when 1 - " like" + " #{I18n.t('core.like').downcase}" else - " likes" + " #{I18n.t('core.like').downcase}s" end end diff --git a/app/javascript/Search/SearchForm.jsx b/app/javascript/Search/SearchForm.jsx index ab1de7f32..8c4955e84 100644 --- a/app/javascript/Search/SearchForm.jsx +++ b/app/javascript/Search/SearchForm.jsx @@ -1,8 +1,10 @@ import PropTypes from 'prop-types'; import { h } from 'preact'; import { forwardRef } from 'preact/compat'; +import { locale } from '../utilities/locale'; import { Button } from '@crayons'; + const SearchIcon = () => (