summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2017-05-05 02:33:30 +0000
committerDouwe Maan <douwe@gitlab.com>2017-05-05 02:33:30 +0000
commit64014b1595c6c55e01f017bb01c189a74726a8a0 (patch)
tree49f4adabaff8da802f246655b03ac81a049146f0
parentbacf652a3794c4da71312982b7617542363c1441 (diff)
parent7f24b87b3beb142cadc7644db3ab4c49fdd04839 (diff)
downloadgitlab-ce-64014b1595c6c55e01f017bb01c189a74726a8a0.tar.gz
Merge branch '28433-internationalise-cycle-analytics-page' into 'master'
Internationalise Cycle Analytics page Closes #28433 See merge request !10669
-rw-r--r--.eslintignore1
-rw-r--r--.gitignore3
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock18
-rw-r--r--app/assets/javascripts/cycle_analytics/components/limit_warning_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_issue_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_production_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.js8
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js3
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js17
-rw-r--r--app/assets/javascripts/locale/de/app.js1
-rw-r--r--app/assets/javascripts/locale/en/app.js1
-rw-r--r--app/assets/javascripts/locale/es/app.js1
-rw-r--r--app/assets/javascripts/locale/index.js70
-rw-r--r--app/assets/javascripts/vue_shared/translate.js42
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss12
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/models/user.rb1
-rw-r--r--app/serializers/analytics_stage_entity.rb1
-rw-r--r--app/serializers/analytics_summary_entity.rb5
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/profiles/show.html.haml5
-rw-r--r--app/views/projects/cycle_analytics/_empty_stage.html.haml2
-rw-r--r--app/views/projects/cycle_analytics/_no_access.html.haml4
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml35
-rw-r--r--changelogs/unreleased/implement-i18n-support.yml4
-rw-r--r--config/application.rb3
-rw-r--r--config/initializers/fast_gettext.rb5
-rw-r--r--config/initializers/gettext_rails_i18n_patch.rb42
-rw-r--r--config/locales/de.yml219
-rw-r--r--config/locales/es.yml217
-rw-r--r--config/webpack.config.js14
-rw-r--r--db/migrate/20170413035209_add_preferred_language_to_users.rb16
-rw-r--r--db/schema.rb1
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb2
-rw-r--r--lib/gitlab/cycle_analytics/code_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/issue_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/plan_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/production_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/review_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/staging_stage.rb8
-rw-r--r--lib/gitlab/cycle_analytics/summary/base.rb2
-rw-r--r--lib/gitlab/cycle_analytics/summary/commit.rb4
-rw-r--r--lib/gitlab/cycle_analytics/summary/deploy.rb4
-rw-r--r--lib/gitlab/cycle_analytics/summary/issue.rb2
-rw-r--r--lib/gitlab/cycle_analytics/test_stage.rb8
-rw-r--r--lib/gitlab/i18n.rb26
-rw-r--r--lib/tasks/gettext.rake14
-rw-r--r--locale/de/gitlab.po207
-rw-r--r--locale/de/gitlab.po.time_stamp0
-rw-r--r--locale/en/gitlab.po207
-rw-r--r--locale/en/gitlab.po.time_stamp0
-rw-r--r--locale/es/gitlab.po208
-rw-r--r--locale/es/gitlab.po.time_stamp0
-rw-r--r--locale/gitlab.pot208
-rw-r--r--package.json2
-rw-r--r--spec/features/cycle_analytics_spec.rb19
-rw-r--r--spec/javascripts/cycle_analytics/limit_warning_component_spec.js3
-rw-r--r--spec/javascripts/vue_shared/translate_spec.js90
-rw-r--r--spec/lib/gitlab/i18n_spec.rb27
-rw-r--r--spec/models/user_spec.rb8
-rw-r--r--yarn.lock19
69 files changed, 1829 insertions, 81 deletions
diff --git a/.eslintignore b/.eslintignore
index c742b08c005..1605e483e9e 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -7,3 +7,4 @@
/vendor/
karma.config.js
webpack.config.js
+/app/assets/javascripts/locale/**/*.js
diff --git a/.gitignore b/.gitignore
index bb818213de1..0fb97ffb98e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
*.log
*.swp
+*.mo
+*.edit.po
.DS_Store
.bundle
.chef
@@ -54,3 +56,4 @@ eslint-report.html
/shared/*
/.gitlab_workhorse_secret
/webpack-report/
+/locale/**/LC_MESSAGES
diff --git a/Gemfile b/Gemfile
index f54a1f500fd..9426a558612 100644
--- a/Gemfile
+++ b/Gemfile
@@ -256,6 +256,11 @@ gem 'sentry-raven', '~> 2.4.0'
gem 'premailer-rails', '~> 1.9.0'
+# I18n
+gem 'gettext_i18n_rails', '~> 1.8.0'
+gem 'gettext_i18n_rails_js', '~> 1.2.0'
+gem 'gettext', '~> 3.2.2', require: false, group: :development
+
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
diff --git a/Gemfile.lock b/Gemfile.lock
index b822a325861..c7e3f9935da 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -198,6 +198,7 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
+ fast_gettext (1.4.0)
ffaker (2.4.0)
ffi (1.9.10)
flay (2.8.1)
@@ -251,6 +252,16 @@ GEM
gemojione (3.0.1)
json
get_process_mem (0.2.0)
+ gettext (3.2.2)
+ locale (>= 2.0.5)
+ text (>= 1.3.0)
+ gettext_i18n_rails (1.8.0)
+ fast_gettext (>= 0.9.0)
+ gettext_i18n_rails_js (1.2.0)
+ gettext (>= 3.0.2)
+ gettext_i18n_rails (>= 0.7.1)
+ po_to_json (>= 1.0.0)
+ rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.5.0)
google-protobuf (~> 3.1)
@@ -422,6 +433,7 @@ GEM
licensee (8.7.0)
rugged (~> 0.24)
little-plugger (1.1.4)
+ locale (2.1.2)
logging (2.1.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
@@ -525,6 +537,8 @@ GEM
ast (~> 2.2)
path_expander (1.0.1)
pg (0.18.4)
+ po_to_json (1.0.1)
+ json (>= 1.6.0)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -777,6 +791,7 @@ GEM
temple (0.7.7)
test_after_commit (1.1.0)
activerecord (>= 3.2)
+ text (1.3.1)
thin (1.7.0)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
@@ -904,6 +919,9 @@ DEPENDENCIES
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
+ gettext (~> 3.2.2)
+ gettext_i18n_rails (~> 1.8.0)
+ gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.5.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js
index abe48572347..8d3d34f836f 100644
--- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js
@@ -9,9 +9,9 @@ export default {
<span v-if="count === 50" class="events-info pull-right">
<i class="fa fa-warning has-tooltip"
aria-hidden="true"
- title="Limited to showing 50 events at most"
+ :title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
data-placement="top"></i>
- Showing 50 events
+ {{ n__('Showing %d event', 'Showing %d events', 50) }}
</span>
`,
};
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
index 80bd2df6f42..0d9ad197abf 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
@@ -28,11 +28,11 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot;
<span>
- Opened
+ {{ __('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span>
<span>
- by
+ {{ __('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span>
</div>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
index 20a43798fbe..ad285874643 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
@@ -28,11 +28,11 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot;
<span>
- Opened
+ {{ __('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span>
<span>
- by
+ {{ __('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }}
</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index f33cac3da82..222084deee9 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
@@ -31,10 +31,10 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
</a>
</h5>
<span>
- First
+ {{ __('FirstPushedBy|First') }}
<span class="commit-icon">${iconCommit}</span>
<a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
- pushed by
+ {{ __('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link">
{{ commit.author.name }}
</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
index 657f5385374..a14ebc3ece9 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
@@ -28,11 +28,11 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot;
<span>
- Opened
+ {{ __('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span>
<span>
- by
+ {{ __('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }}
</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
index 8a801300647..1a5bf9bc0b5 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
@@ -28,11 +28,11 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot;
<span>
- Opened
+ {{ __('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span>
<span>
- by
+ {{ __('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span>
<template v-if="mergeRequest.state === 'closed'">
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index 4a286379588..b1e9362434f 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
@@ -32,7 +32,7 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>
- by
+ {{ __('ByAuthor|by') }}
<a :href="build.author.webUrl" class="issue-author-link">
{{ build.author.name }}
</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
index 77edcb76273..d5e6167b2a8 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
@@ -12,10 +12,10 @@ global.cycleAnalytics.TotalTimeComponent = Vue.extend({
template: `
<span class="total-time">
<template v-if="Object.keys(time).length">
- <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
- <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
- <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
- <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
+ <template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
+ <template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
+ <template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
+ <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
</template>
<template v-else>
--
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 48cab437e02..c8e53cb554e 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -2,6 +2,7 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
+import Translate from '../vue_shared/translate';
import LimitWarningComponent from './components/limit_warning_component';
require('./components/stage_code_component');
@@ -16,6 +17,8 @@ require('./cycle_analytics_service');
require('./cycle_analytics_store');
require('./default_event_objects');
+Vue.use(Translate);
+
$(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 681d6eef565..6504d7db2f2 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -30,7 +30,7 @@ class CycleAnalyticsService {
startDate,
} = options;
- return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
+ return $.get(`${this.requestPath}/events/${stage.name}.json`, {
cycle_analytics: {
start_date: startDate,
},
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index 6536a8fd7fa..50bd394e90e 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
@@ -1,4 +1,5 @@
/* eslint-disable no-param-reassign */
+import { __ } from '../locale';
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
@@ -7,13 +8,13 @@ const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
const EMPTY_STAGE_TEXTS = {
- issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
- plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
- code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
- test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
- review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
- staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
- production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
+ issue: __('The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.'),
+ plan: __('The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.'),
+ code: __('The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.'),
+ test: __('The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.'),
+ review: __('The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.'),
+ staging: __('The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.'),
+ production: __('The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.'),
};
global.cycleAnalytics.CycleAnalyticsStore = {
@@ -38,7 +39,7 @@ global.cycleAnalytics.CycleAnalyticsStore = {
});
newData.stages.forEach((item) => {
- const stageSlug = gl.text.dasherize(item.title.toLowerCase());
+ const stageSlug = gl.text.dasherize(item.name.toLowerCase());
item.active = false;
item.isUserAllowed = data.permissions[stageSlug];
item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
diff --git a/app/assets/javascripts/locale/de/app.js b/app/assets/javascripts/locale/de/app.js
new file mode 100644
index 00000000000..e96090da80e
--- /dev/null
+++ b/app/assets/javascripts/locale/de/app.js
@@ -0,0 +1 @@
+var locales = locales || {}; locales['de'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:37-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"German","Language":"de","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"de","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"ByAuthor|by":[""],"Commit":["",""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Deploy":["",""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Introducing Cycle Analytics":[""],"Last %d day":["",""],"Limited to showing %d event at most":["",""],"Median":[""],"New Issue":["",""],"Not available":[""],"Not enough data":[""],"OpenedNDaysAgo|Opened":[""],"Pipeline Health":[""],"ProjectLifecycle|Stage":[""],"Read more":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Showing %d event":["",""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":[""],"The collection of events added to the data gathered for that stage.":[""],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":[""],"The phase of the development lifecycle.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":[""],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":[""],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":[""],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":[""],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":[""],"The time taken by each data entry gathered by that stage.":[""],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"You need permission.":[""],"day":["",""]}}}; \ No newline at end of file
diff --git a/app/assets/javascripts/locale/en/app.js b/app/assets/javascripts/locale/en/app.js
new file mode 100644
index 00000000000..ade9b667b3c
--- /dev/null
+++ b/app/assets/javascripts/locale/en/app.js
@@ -0,0 +1 @@
+var locales = locales || {}; locales['en'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:36-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"English","Language":"en","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"en","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"ByAuthor|by":[""],"Commit":["",""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Deploy":["",""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Introducing Cycle Analytics":[""],"Last %d day":["",""],"Limited to showing %d event at most":["",""],"Median":[""],"New Issue":["",""],"Not available":[""],"Not enough data":[""],"OpenedNDaysAgo|Opened":[""],"Pipeline Health":[""],"ProjectLifecycle|Stage":[""],"Read more":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Showing %d event":["",""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":[""],"The collection of events added to the data gathered for that stage.":[""],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":[""],"The phase of the development lifecycle.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":[""],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":[""],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":[""],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":[""],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":[""],"The time taken by each data entry gathered by that stage.":[""],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"You need permission.":[""],"day":["",""]}}}; \ No newline at end of file
diff --git a/app/assets/javascripts/locale/es/app.js b/app/assets/javascripts/locale/es/app.js
new file mode 100644
index 00000000000..3dafa21f235
--- /dev/null
+++ b/app/assets/javascripts/locale/es/app.js
@@ -0,0 +1 @@
+var locales = locales || {}; locales['es'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Language-Team":"Spanish","Language":"es","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"","X-Generator":"Poedit 2.0.1","lang":"es","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"ByAuthor|by":["por"],"Commit":["Cambio","Cambios"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Incidencia"],"CycleAnalyticsStage|Plan":["Planificación"],"CycleAnalyticsStage|Production":["Producción"],"CycleAnalyticsStage|Review":["Revisión"],"CycleAnalyticsStage|Staging":["Puesta en escena"],"CycleAnalyticsStage|Test":["Pruebas"],"Deploy":["Despliegue","Despliegues"],"FirstPushedBy|First":["Primer"],"FirstPushedBy|pushed by":["enviado por"],"From issue creation until deploy to production":["Desde la creación de la incidencia hasta el despliegue a producción"],"From merge request merge until deploy to production":["Desde la integración de la solicitud de fusión hasta el despliegue a producción"],"Introducing Cycle Analytics":["Introducción a Cycle Analytics"],"Last %d day":["Último %d día","Últimos %d días"],"Limited to showing %d event at most":["Limitado a mostrar máximo %d evento","Limitado a mostrar máximo %d eventos"],"Median":["Mediana"],"New Issue":["Nueva incidencia","Nuevas incidencias"],"Not available":["No disponible"],"Not enough data":["No hay suficientes datos"],"OpenedNDaysAgo|Opened":["Abierto"],"Pipeline Health":["Estado del Pipeline"],"ProjectLifecycle|Stage":["Etapa"],"Read more":["Leer más"],"Related Commits":["Cambios Relacionados"],"Related Deployed Jobs":["Trabajos Desplegados Relacionados"],"Related Issues":["Incidencias Relacionadas"],"Related Jobs":["Trabajos Relacionados"],"Related Merge Requests":["Solicitudes de fusión Relacionadas"],"Related Merged Requests":["Solicitudes de fusión Relacionadas"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapa de codificación muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquí una vez creada tu primera solicitud de fusión."],"The collection of events added to the data gathered for that stage.":["La colección de eventos agregados a los datos recopilados para esa etapa."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."],"The phase of the development lifecycle.":["La etapa del ciclo de vida de desarrollo."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapa de planificación muestra el tiempo desde el paso anterior hasta el envío de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envíe el primer cambio."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."],"The time taken by each data entry gathered by that stage.":["El tiempo utilizado por cada entrada de datos obtenido por esa etapa."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."],"Time before an issue gets scheduled":["Tiempo antes de que una incidencia sea programada"],"Time before an issue starts implementation":["Tiempo antes de que empieze la implementación de una incidencia"],"Time between merge request creation and merge/close":["Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"],"Time until first merge request":["Tiempo hasta la primera solicitud de fusión"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tiempo Total"],"Total test time for all commits/merges":["Tiempo total de pruebas para todos los cambios o integraciones"],"Want to see the data? Please ask an administrator for access.":["¿Quieres ver los datos? Por favor pide acceso al administrador."],"We don't have enough data to show this stage.":["No hay suficientes datos para mostrar en esta etapa."],"You need permission.":["Necesitas permisos."],"day":["día","días"]}}}; \ No newline at end of file
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
new file mode 100644
index 00000000000..7ba676d6d20
--- /dev/null
+++ b/app/assets/javascripts/locale/index.js
@@ -0,0 +1,70 @@
+import Jed from 'jed';
+
+/**
+ This is required to require all the translation folders in the current directory
+ this saves us having to do this manually & keep up to date with new languages
+**/
+function requireAll(requireContext) { return requireContext.keys().map(requireContext); }
+
+const allLocales = requireAll(require.context('./', true, /^(?!.*(?:index.js$)).*\.js$/));
+const locales = allLocales.reduce((d, obj) => {
+ const data = d;
+ const localeKey = Object.keys(obj)[0];
+
+ data[localeKey] = obj[localeKey];
+
+ return data;
+}, {});
+
+let lang = document.querySelector('html').getAttribute('lang') || 'en';
+lang = lang.replace(/-/g, '_');
+
+const locale = new Jed(locales[lang]);
+
+/**
+ Translates `text`
+
+ @param text The text to be translated
+ @returns {String} The translated text
+**/
+const gettext = locale.gettext.bind(locale);
+
+/**
+ Translate the text with a number
+ if the number is more than 1 it will use the `pluralText` translation.
+ This method allows for contexts, see below re. contexts
+
+ @param text Singular text to translate (eg. '%d day')
+ @param pluralText Plural text to translate (eg. '%d days')
+ @param count Number to decide which translation to use (eg. 2)
+ @returns {String} Translated text with the number replaced (eg. '2 days')
+**/
+const ngettext = (text, pluralText, count) => {
+ const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|');
+
+ return translated[translated.length - 1];
+};
+
+/**
+ Translate context based text
+ Either pass in the context translation like `Context|Text to translate`
+ or allow for dynamic text by doing passing in the context first & then the text to translate
+
+ @param keyOrContext Can be either the key to translate including the context
+ (eg. 'Context|Text') or just the context for the translation
+ (eg. 'Context')
+ @param key Is the dynamic variable you want to be translated
+ @returns {String} Translated context based text
+**/
+const pgettext = (keyOrContext, key) => {
+ const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext;
+ const translated = gettext(normalizedKey).split('|');
+
+ return translated[translated.length - 1];
+};
+
+export { lang };
+export { gettext as __ };
+export { ngettext as n__ };
+export { pgettext as s__ };
+export default locale;
diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js
new file mode 100644
index 00000000000..f83c4b00761
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/translate.js
@@ -0,0 +1,42 @@
+import {
+ __,
+ n__,
+ s__,
+} from '../locale';
+
+export default (Vue) => {
+ Vue.mixin({
+ methods: {
+ /**
+ Translates `text`
+
+ @param text The text to be translated
+ @returns {String} The translated text
+ **/
+ __,
+ /**
+ Translate the text with a number
+ if the number is more than 1 it will use the `pluralText` translation.
+ This method allows for contexts, see below re. contexts
+
+ @param text Singular text to translate (eg. '%d day')
+ @param pluralText Plural text to translate (eg. '%d days')
+ @param count Number to decide which translation to use (eg. 2)
+ @returns {String} Translated text with the number replaced (eg. '2 days')
+ **/
+ n__,
+ /**
+ Translate context based text
+ Either pass in the context translation like `Context|Text to translate`
+ or allow for dynamic text by doing passing in the context first & then the text to translate
+
+ @param keyOrContext Can be either the key to translate including the context
+ (eg. 'Context|Text') or just the context for the translation
+ (eg. 'Context')
+ @param key Is the dynamic variable you want to be translated
+ @returns {String} Translated context based text
+ **/
+ s__,
+ },
+ });
+};
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 403724cd68a..52bbb753af3 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -175,7 +175,7 @@
}
.stage-nav-item {
- display: block;
+ display: flex;
line-height: 65px;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
@@ -209,14 +209,10 @@
}
.stage-nav-item-cell {
- float: left;
-
- &.stage-name {
- width: 65%;
- }
-
&.stage-median {
- width: 35%;
+ margin-left: auto;
+ margin-right: $gl-padding;
+ min-width: calc(35% - #{$gl-padding});
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e48f0963ef4..d2c13da6917 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -21,6 +21,8 @@ class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
+ around_action :set_locale
+
protect_from_forgery with: :exception
helper_method :can?, :current_application_settings
@@ -269,4 +271,12 @@ class ApplicationController < ActionController::Base
def u2f_app_id
request.base_url
end
+
+ def set_locale
+ Gitlab::I18n.set_locale(current_user)
+
+ yield
+ ensure
+ Gitlab::I18n.reset_locale
+ end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 987b95e89b9..57e23cea00e 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -85,7 +85,8 @@ class ProfilesController < Profiles::ApplicationController
:twitter,
:username,
:website_url,
- :organization
+ :organization,
+ :preferred_language
)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 2b7ebe6c1a7..43c5fdc038d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -23,6 +23,7 @@ class User < ActiveRecord::Base
default_value_for :hide_no_password, false
default_value_for :project_view, :files
default_value_for :notified_of_own_activity, false
+ default_value_for :preferred_language, I18n.default_locale
attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base,
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index 69bf693de8d..564612202b5 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity
include EntityDateHelper
expose :title
+ expose :name
expose :legend
expose :description
diff --git a/app/serializers/analytics_summary_entity.rb b/app/serializers/analytics_summary_entity.rb
index 91803ec07f5..9c37afd53e1 100644
--- a/app/serializers/analytics_summary_entity.rb
+++ b/app/serializers/analytics_summary_entity.rb
@@ -1,7 +1,4 @@
class AnalyticsSummaryEntity < Grape::Entity
expose :value, safe: true
-
- expose :title do |object|
- object.title.pluralize(object.value)
- end
+ expose :title
end
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 36543edc040..dc926a615c7 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,5 +1,5 @@
!!! 5
-%html{ lang: "en", class: "#{page_class}" }
+%html{ lang: I18n.locale, class: "#{page_class}" }
= render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= Gon::Base.render_data
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index c74b3249a13..4a1438aa68e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -73,6 +73,11 @@
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), { include_blank: 'Do not show on profile' }, class: "select2"
%span.help-block This email will be displayed on your public profile.
.form-group
+ = f.label :preferred_language, class: "label-light"
+ = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
+ {}, class: "select2"
+ %span.help-block This feature is experimental and translations are not complete yet.
+ .form-group
= f.label :skype, class: "label-light"
= f.text_field :skype, class: "form-control"
.form-group
diff --git a/app/views/projects/cycle_analytics/_empty_stage.html.haml b/app/views/projects/cycle_analytics/_empty_stage.html.haml
index c3f95860e92..cdad0bc7231 100644
--- a/app/views/projects/cycle_analytics/_empty_stage.html.haml
+++ b/app/views/projects/cycle_analytics/_empty_stage.html.haml
@@ -2,6 +2,6 @@
.empty-stage
.icon-no-data
= custom_icon ('icon_no_data')
- %h4 We don't have enough data to show this stage.
+ %h4 {{ __('We don\'t have enough data to show this stage.') }}
%p
{{currentStage.emptyStageText}}
diff --git a/app/views/projects/cycle_analytics/_no_access.html.haml b/app/views/projects/cycle_analytics/_no_access.html.haml
index 0ffc79b3181..c3eda398234 100644
--- a/app/views/projects/cycle_analytics/_no_access.html.haml
+++ b/app/views/projects/cycle_analytics/_no_access.html.haml
@@ -2,6 +2,6 @@
.no-access-stage
.icon-lock
= custom_icon ('icon_lock')
- %h4 You need permission.
+ %h4 {{ __('You need permission.') }}
%p
- Want to see the data? Please ask administrator for access.
+ {{ __('Want to see the data? Please ask an administrator for access.') }}
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index dd3fa814716..819f29d3ca5 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -2,6 +2,7 @@
- page_title "Cycle Analytics"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('locale')
= page_specific_javascript_bundle_tag('cycle_analytics')
= render "projects/head"
@@ -15,16 +16,16 @@
= custom_icon('icon_cycle_analytics_splash')
.col-sm-8.col-xs-12.inner-content
%h4
- Introducing Cycle Analytics
+ {{ __('Introducing Cycle Analytics') }}
%p
- Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
+ {{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
- = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
+ = link_to _('Read more'), help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= icon("spinner spin", "v-show" => "isLoading")
.wrapper{ "v-show" => "!isLoading && !hasError" }
.panel.panel-default
.panel-heading
- Pipeline Health
+ {{ __('Pipeline Health') }}
.content-block
.container-fluid
.row
@@ -34,15 +35,15 @@
.col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
- %span.dropdown-label Last 30 days
+ %span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
%i.fa.fa-chevron-down
%ul.dropdown-menu.dropdown-menu-align-right
%li
%a{ "href" => "#", "data-value" => "30" }
- Last 30 days
+ {{ n__('Last %d day', 'Last %d days', 30) }}
%li
%a{ "href" => "#", "data-value" => "90" }
- Last 90 days
+ {{ n__('Last %d day', 'Last %d days', 90) }}
.stage-panel-container
.panel.panel-default.stage-panel
.panel-heading
@@ -50,20 +51,20 @@
%ul
%li.stage-header
%span.stage-name
- Stage
- %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: "The phase of the development lifecycle.", "aria-hidden" => "true" }
+ {{ __('ProjectLifecycle|Stage') }}
+ %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The phase of the development lifecycle."), "aria-hidden" => "true" }
%li.median-header
%span.stage-name
- Median
- %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.", "aria-hidden" => "true" }
+ {{ __('Median') }}
+ %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."), "aria-hidden" => "true" }
%li.event-header
%span.stage-name
- {{ currentStage ? currentStage.legend : 'Related Issues' }}
- %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: "The collection of events added to the data gathered for that stage.", "aria-hidden" => "true" }
+ {{ currentStage ? __(currentStage.legend) : __('Related Issues') }}
+ %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The collection of events added to the data gathered for that stage."), "aria-hidden" => "true" }
%li.total-time-header
%span.stage-name
- Total Time
- %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: "The time taken by each data entry gathered by that stage.", "aria-hidden" => "true" }
+ {{ __('Total Time') }}
+ %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The time taken by each data entry gathered by that stage."), "aria-hidden" => "true" }
.stage-panel-body
%nav.stage-nav
%ul
@@ -75,10 +76,10 @@
%span{ "v-if" => "stage.value" }
{{ stage.value }}
%span.stage-empty{ "v-else" => true }
- Not enough data
+ {{ __('Not enough data') }}
%template{ "v-else" => true }
%span.not-available
- Not available
+ {{ __('Not available') }}
.section.stage-events
%template{ "v-if" => "isLoadingStage" }
= icon("spinner spin")
diff --git a/changelogs/unreleased/implement-i18n-support.yml b/changelogs/unreleased/implement-i18n-support.yml
new file mode 100644
index 00000000000..d304fbecf90
--- /dev/null
+++ b/changelogs/unreleased/implement-i18n-support.yml
@@ -0,0 +1,4 @@
+---
+title: Add support for i18n on Cycle Analytics page
+merge_request: 10669
+author:
diff --git a/config/application.rb b/config/application.rb
index f2ecc4ce77c..32ad2393648 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -40,6 +40,9 @@ module Gitlab
# config.i18n.default_locale = :de
config.i18n.enforce_available_locales = false
+ # Translation for AR attrs is not working well for POROs like WikiPage
+ config.gettext_i18n_rails.use_for_active_record_attributes = false
+
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
diff --git a/config/initializers/fast_gettext.rb b/config/initializers/fast_gettext.rb
new file mode 100644
index 00000000000..bea85b43b95
--- /dev/null
+++ b/config/initializers/fast_gettext.rb
@@ -0,0 +1,5 @@
+FastGettext.add_text_domain 'gitlab', path: 'locale', type: :po
+FastGettext.default_text_domain = 'gitlab'
+FastGettext.default_available_locales = Gitlab::I18n.available_locales
+
+I18n.available_locales = Gitlab::I18n.available_locales
diff --git a/config/initializers/gettext_rails_i18n_patch.rb b/config/initializers/gettext_rails_i18n_patch.rb
new file mode 100644
index 00000000000..69118f464ca
--- /dev/null
+++ b/config/initializers/gettext_rails_i18n_patch.rb
@@ -0,0 +1,42 @@
+require 'gettext_i18n_rails/haml_parser'
+require 'gettext_i18n_rails_js/parser/javascript'
+
+VUE_TRANSLATE_REGEX = /((%[\w.-]+)(?:\s))?{{ (N|n|s)?__\((.*)\) }}/
+
+module GettextI18nRails
+ class HamlParser
+ singleton_class.send(:alias_method, :old_convert_to_code, :convert_to_code)
+
+ # We need to convert text in Mustache format
+ # to a format that can be parsed by Gettext scripts.
+ # If we found a content like "{{ __('Stage') }}"
+ # in a HAML file we convert it to "= _('Stage')", that way
+ # it can be processed by the "rake gettext:find" script.
+ #
+ # Overwrites: https://github.com/grosser/gettext_i18n_rails/blob/8396387a431e0f8ead72fc1cd425cad2fa4992f2/lib/gettext_i18n_rails/haml_parser.rb#L9
+ def self.convert_to_code(text)
+ text.gsub!(VUE_TRANSLATE_REGEX, "\\2= \\3_(\\4)")
+
+ old_convert_to_code(text)
+ end
+ end
+end
+
+module GettextI18nRailsJs
+ module Parser
+ module Javascript
+ # This is required to tell the `rake gettext:find` script to use the Javascript
+ # parser for *.vue files.
+ #
+ # Overwrites: https://github.com/webhippie/gettext_i18n_rails_js/blob/46c58db6d2053a4f5f36a0eb024ea706ff5707cb/lib/gettext_i18n_rails_js/parser/javascript.rb#L36
+ def target?(file)
+ [
+ ".js",
+ ".jsx",
+ ".coffee",
+ ".vue"
+ ].include? ::File.extname(file)
+ end
+ end
+ end
+end
diff --git a/config/locales/de.yml b/config/locales/de.yml
new file mode 100644
index 00000000000..533663a2704
--- /dev/null
+++ b/config/locales/de.yml
@@ -0,0 +1,219 @@
+---
+de:
+ activerecord:
+ errors:
+ messages:
+ record_invalid: 'Gültigkeitsprüfung ist fehlgeschlagen: %{errors}'
+ restrict_dependent_destroy:
+ has_one: Datensatz kann nicht gelöscht werden, da ein abhängiger %{record}-Datensatz
+ existiert.
+ has_many: Datensatz kann nicht gelöscht werden, da abhängige %{record} existieren.
+ date:
+ abbr_day_names:
+ - So
+ - Mo
+ - Di
+ - Mi
+ - Do
+ - Fr
+ - Sa
+ abbr_month_names:
+ -
+ - Jan
+ - Feb
+ - Mär
+ - Apr
+ - Mai
+ - Jun
+ - Jul
+ - Aug
+ - Sep
+ - Okt
+ - Nov
+ - Dez
+ day_names:
+ - Sonntag
+ - Montag
+ - Dienstag
+ - Mittwoch
+ - Donnerstag
+ - Freitag
+ - Samstag
+ formats:
+ default: "%d.%m.%Y"
+ long: "%e. %B %Y"
+ short: "%e. %b"
+ month_names:
+ -
+ - Januar
+ - Februar
+ - März
+ - April
+ - Mai
+ - Juni
+ - Juli
+ - August
+ - September
+ - Oktober
+ - November
+ - Dezember
+ order:
+ - :day
+ - :month
+ - :year
+ datetime:
+ distance_in_words:
+ about_x_hours:
+ one: etwa eine Stunde
+ other: etwa %{count} Stunden
+ about_x_months:
+ one: etwa ein Monat
+ other: etwa %{count} Monate
+ about_x_years:
+ one: etwa ein Jahr
+ other: etwa %{count} Jahre
+ almost_x_years:
+ one: fast ein Jahr
+ other: fast %{count} Jahre
+ half_a_minute: eine halbe Minute
+ less_than_x_minutes:
+ one: weniger als eine Minute
+ other: weniger als %{count} Minuten
+ less_than_x_seconds:
+ one: weniger als eine Sekunde
+ other: weniger als %{count} Sekunden
+ over_x_years:
+ one: mehr als ein Jahr
+ other: mehr als %{count} Jahre
+ x_days:
+ one: ein Tag
+ other: "%{count} Tage"
+ x_minutes:
+ one: eine Minute
+ other: "%{count} Minuten"
+ x_months:
+ one: ein Monat
+ other: "%{count} Monate"
+ x_seconds:
+ one: eine Sekunde
+ other: "%{count} Sekunden"
+ prompts:
+ day: Tag
+ hour: Stunden
+ minute: Minute
+ month: Monat
+ second: Sekunde
+ year: Jahr
+ errors:
+ format: "%{attribute} %{message}"
+ messages:
+ accepted: muss akzeptiert werden
+ blank: muss ausgefüllt werden
+ present: darf nicht ausgefüllt werden
+ confirmation: stimmt nicht mit %{attribute} überein
+ empty: muss ausgefüllt werden
+ equal_to: muss genau %{count} sein
+ even: muss gerade sein
+ exclusion: ist nicht verfügbar
+ greater_than: muss größer als %{count} sein
+ greater_than_or_equal_to: muss größer oder gleich %{count} sein
+ inclusion: ist kein gültiger Wert
+ invalid: ist nicht gültig
+ less_than: muss kleiner als %{count} sein
+ less_than_or_equal_to: muss kleiner oder gleich %{count} sein
+ model_invalid: 'Gültigkeitsprüfung ist fehlgeschlagen: %{errors}'
+ not_a_number: ist keine Zahl
+ not_an_integer: muss ganzzahlig sein
+ odd: muss ungerade sein
+ required: muss ausgefüllt werden
+ taken: ist bereits vergeben
+ too_long:
+ one: ist zu lang (mehr als 1 Zeichen)
+ other: ist zu lang (mehr als %{count} Zeichen)
+ too_short:
+ one: ist zu kurz (weniger als 1 Zeichen)
+ other: ist zu kurz (weniger als %{count} Zeichen)
+ wrong_length:
+ one: hat die falsche Länge (muss genau 1 Zeichen haben)
+ other: hat die falsche Länge (muss genau %{count} Zeichen haben)
+ other_than: darf nicht gleich %{count} sein
+ template:
+ body: 'Bitte überprüfen Sie die folgenden Felder:'
+ header:
+ one: 'Konnte %{model} nicht speichern: ein Fehler.'
+ other: 'Konnte %{model} nicht speichern: %{count} Fehler.'
+ helpers:
+ select:
+ prompt: Bitte wählen
+ submit:
+ create: "%{model} erstellen"
+ submit: "%{model} speichern"
+ update: "%{model} aktualisieren"
+ number:
+ currency:
+ format:
+ delimiter: "."
+ format: "%n %u"
+ precision: 2
+ separator: ","
+ significant: false
+ strip_insignificant_zeros: false
+ unit: "€"
+ format:
+ delimiter: "."
+ precision: 2
+ separator: ","
+ significant: false
+ strip_insignificant_zeros: false
+ human:
+ decimal_units:
+ format: "%n %u"
+ units:
+ billion:
+ one: Milliarde
+ other: Milliarden
+ million:
+ one: Million
+ other: Millionen
+ quadrillion:
+ one: Billiarde
+ other: Billiarden
+ thousand: Tausend
+ trillion:
+ one: Billion
+ other: Billionen
+ unit: ''
+ format:
+ delimiter: ''
+ precision: 3
+ significant: true
+ strip_insignificant_zeros: true
+ storage_units:
+ format: "%n %u"
+ units:
+ byte:
+ one: Byte
+ other: Bytes
+ gb: GB
+ kb: KB
+ mb: MB
+ tb: TB
+ percentage:
+ format:
+ delimiter: ''
+ format: "%n %"
+ precision:
+ format:
+ delimiter: ''
+ support:
+ array:
+ last_word_connector: " und "
+ two_words_connector: " und "
+ words_connector: ", "
+ time:
+ am: vormittags
+ formats:
+ default: "%A, %d. %B %Y, %H:%M Uhr"
+ long: "%A, %d. %B %Y, %H:%M Uhr"
+ short: "%d. %B, %H:%M Uhr"
+ pm: nachmittags
diff --git a/config/locales/es.yml b/config/locales/es.yml
new file mode 100644
index 00000000000..87e79beee74
--- /dev/null
+++ b/config/locales/es.yml
@@ -0,0 +1,217 @@
+---
+es:
+ activerecord:
+ errors:
+ messages:
+ record_invalid: "La validación falló: %{errors}"
+ restrict_dependent_destroy:
+ has_one: No se puede eliminar el registro porque existe un %{record} dependiente
+ has_many: No se puede eliminar el registro porque existen %{record} dependientes
+ date:
+ abbr_day_names:
+ - dom
+ - lun
+ - mar
+ - mié
+ - jue
+ - vie
+ - sáb
+ abbr_month_names:
+ -
+ - ene
+ - feb
+ - mar
+ - abr
+ - may
+ - jun
+ - jul
+ - ago
+ - sep
+ - oct
+ - nov
+ - dic
+ day_names:
+ - domingo
+ - lunes
+ - martes
+ - miércoles
+ - jueves
+ - viernes
+ - sábado
+ formats:
+ default: "%d/%m/%Y"
+ long: "%d de %B de %Y"
+ short: "%d de %b"
+ month_names:
+ -
+ - enero
+ - febrero
+ - marzo
+ - abril
+ - mayo
+ - junio
+ - julio
+ - agosto
+ - septiembre
+ - octubre
+ - noviembre
+ - diciembre
+ order:
+ - :day
+ - :month
+ - :year
+ datetime:
+ distance_in_words:
+ about_x_hours:
+ one: alrededor de 1 hora
+ other: alrededor de %{count} horas
+ about_x_months:
+ one: alrededor de 1 mes
+ other: alrededor de %{count} meses
+ about_x_years:
+ one: alrededor de 1 año
+ other: alrededor de %{count} años
+ almost_x_years:
+ one: casi 1 año
+ other: casi %{count} años
+ half_a_minute: medio minuto
+ less_than_x_minutes:
+ one: menos de 1 minuto
+ other: menos de %{count} minutos
+ less_than_x_seconds:
+ one: menos de 1 segundo
+ other: menos de %{count} segundos
+ over_x_years:
+ one: más de 1 año
+ other: más de %{count} años
+ x_days:
+ one: 1 día
+ other: "%{count} días"
+ x_minutes:
+ one: 1 minuto
+ other: "%{count} minutos"
+ x_months:
+ one: 1 mes
+ other: "%{count} meses"
+ x_years:
+ one: 1 año
+ other: "%{count} años"
+ x_seconds:
+ one: 1 segundo
+ other: "%{count} segundos"
+ prompts:
+ day: Día
+ hour: Hora
+ minute: Minutos
+ month: Mes
+ second: Segundos
+ year: Año
+ errors:
+ format: "%{attribute} %{message}"
+ messages:
+ accepted: debe ser aceptado
+ blank: no puede estar en blanco
+ present: debe estar en blanco
+ confirmation: no coincide
+ empty: no puede estar vacío
+ equal_to: debe ser igual a %{count}
+ even: debe ser par
+ exclusion: está reservado
+ greater_than: debe ser mayor que %{count}
+ greater_than_or_equal_to: debe ser mayor que o igual a %{count}
+ inclusion: no está incluido en la lista
+ invalid: no es válido
+ less_than: debe ser menor que %{count}
+ less_than_or_equal_to: debe ser menor que o igual a %{count}
+ model_invalid: "La validación falló: %{errors}"
+ not_a_number: no es un número
+ not_an_integer: debe ser un entero
+ odd: debe ser impar
+ required: debe existir
+ taken: ya está en uso
+ too_long:
+ one: "es demasiado largo (1 carácter máximo)"
+ other: "es demasiado largo (%{count} caracteres máximo)"
+ too_short:
+ one: "es demasiado corto (1 carácter mínimo)"
+ other: "es demasiado corto (%{count} caracteres mínimo)"
+ wrong_length:
+ one: "no tiene la longitud correcta (1 carácter exactos)"
+ other: "no tiene la longitud correcta (%{count} caracteres exactos)"
+ other_than: debe ser distinto de %{count}
+ template:
+ body: 'Se encontraron problemas con los siguientes campos:'
+ header:
+ one: No se pudo guardar este/a %{model} porque se encontró 1 error
+ other: No se pudo guardar este/a %{model} porque se encontraron %{count} errores
+ helpers:
+ select:
+ prompt: Por favor seleccione
+ submit:
+ create: Crear %{model}
+ submit: Guardar %{model}
+ update: Actualizar %{model}
+ number:
+ currency:
+ format:
+ delimiter: "."
+ format: "%n %u"
+ precision: 2
+ separator: ","
+ significant: false
+ strip_insignificant_zeros: false
+ unit: "€"
+ format:
+ delimiter: "."
+ precision: 3
+ separator: ","
+ significant: false
+ strip_insignificant_zeros: false
+ human:
+ decimal_units:
+ format: "%n %u"
+ units:
+ billion: mil millones
+ million:
+ one: millón
+ other: millones
+ quadrillion: mil billones
+ thousand: mil
+ trillion:
+ one: billón
+ other: billones
+ unit: ''
+ format:
+ delimiter: ''
+ precision: 1
+ significant: true
+ strip_insignificant_zeros: true
+ storage_units:
+ format: "%n %u"
+ units:
+ byte:
+ one: Byte
+ other: Bytes
+ gb: GB
+ kb: KB
+ mb: MB
+ tb: TB
+ percentage:
+ format:
+ delimiter: ''
+ format: "%n %"
+ precision:
+ format:
+ delimiter: ''
+ support:
+ array:
+ last_word_connector: " y "
+ two_words_connector: " y "
+ words_connector: ", "
+ time:
+ am: am
+ formats:
+ default: "%A, %d de %B de %Y %H:%M:%S %z"
+ long: "%d de %B de %Y %H:%M"
+ short: "%d de %b %H:%M"
+ pm: pm
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 239bb5ec436..3abdb6c2d76 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -35,7 +35,9 @@ var config = {
group: './group.js',
groups_list: './groups_list.js',
issuable: './issuable/issuable_bundle.js',
+ locale: './locale/index.js',
issue_show: './issue_show/index.js',
+ locale: './locale/index.js',
main: './main.js',
merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js',
merge_request_widget: './merge_request_widget/ci_bundle.js',
@@ -88,6 +90,10 @@ var config = {
exclude: /node_modules/,
loader: 'file-loader',
},
+ {
+ test: /locale\/[a-z]+\/(.*)\.js$/,
+ loader: 'exports-loader?locales',
+ },
]
},
@@ -153,6 +159,14 @@ var config = {
new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'common', 'runtime'],
}),
+
+ // locale common library
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'locale',
+ chunks: [
+ 'cycle_analytics',
+ ],
+ }),
],
resolve: {
diff --git a/db/migrate/20170413035209_add_preferred_language_to_users.rb b/db/migrate/20170413035209_add_preferred_language_to_users.rb
new file mode 100644
index 00000000000..92f1d6f2436
--- /dev/null
+++ b/db/migrate/20170413035209_add_preferred_language_to_users.rb
@@ -0,0 +1,16 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPreferredLanguageToUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :users, :preferred_language, :string
+ end
+
+ def down
+ remove_column :users, :preferred_language
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5d3e22d6123..132549ab34c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1328,6 +1328,7 @@ ActiveRecord::Schema.define(version: 20170503004425) do
t.boolean "notified_of_own_activity"
t.boolean "require_two_factor_authentication_from_group", default: false, null: false
t.integer "two_factor_grace_period", default: 48, null: false
+ t.string "preferred_language"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 1bf20f76ad6..302ccd3e532 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -44,6 +44,9 @@ module API
end
before { allow_access_with_scope :api }
+ before { Gitlab::I18n.set_locale(current_user) }
+
+ after { Gitlab::I18n.reset_locale }
rescue_from Gitlab::Access::AccessDeniedError do
rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index 559e3939da6..cac31ea8cff 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -17,7 +17,7 @@ module Gitlab
end
def title
- name.to_s.capitalize
+ raise NotImplementedError.new("Expected #{self.name} to implement title")
end
def median
diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb
index 1e52b6614a1..5f9dc9a4303 100644
--- a/lib/gitlab/cycle_analytics/code_stage.rb
+++ b/lib/gitlab/cycle_analytics/code_stage.rb
@@ -13,12 +13,16 @@ module Gitlab
:code
end
+ def title
+ s_('CycleAnalyticsStage|Code')
+ end
+
def legend
- "Related Merge Requests"
+ _("Related Merge Requests")
end
def description
- "Time until first merge request"
+ _("Time until first merge request")
end
end
end
diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb
index 213994988a5..7b03811efb2 100644
--- a/lib/gitlab/cycle_analytics/issue_stage.rb
+++ b/lib/gitlab/cycle_analytics/issue_stage.rb
@@ -14,12 +14,16 @@ module Gitlab
:issue
end
+ def title
+ s_('CycleAnalyticsStage|Issue')
+ end
+
def legend
- "Related Issues"
+ _("Related Issues")
end
def description
- "Time before an issue gets scheduled"
+ _("Time before an issue gets scheduled")
end
end
end
diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb
index 45d51d30ccc..1a0afb56b4f 100644
--- a/lib/gitlab/cycle_analytics/plan_stage.rb
+++ b/lib/gitlab/cycle_analytics/plan_stage.rb
@@ -14,12 +14,16 @@ module Gitlab
:plan
end
+ def title
+ s_('CycleAnalyticsStage|Plan')
+ end
+
def legend
- "Related Commits"
+ _("Related Commits")
end
def description
- "Time before an issue starts implementation"
+ _("Time before an issue starts implementation")
end
end
end
diff --git a/lib/gitlab/cycle_analytics/production_stage.rb b/lib/gitlab/cycle_analytics/production_stage.rb
index 9f387a02945..0fa8a65cb99 100644
--- a/lib/gitlab/cycle_analytics/production_stage.rb
+++ b/lib/gitlab/cycle_analytics/production_stage.rb
@@ -15,12 +15,16 @@ module Gitlab
:production
end
+ def title
+ s_('CycleAnalyticsStage|Production')
+ end
+
def legend
- "Related Issues"
+ _("Related Issues")
end
def description
- "From issue creation until deploy to production"
+ _("From issue creation until deploy to production")
end
def query
diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb
index 80e5b538c6c..cfbbdc43fd9 100644
--- a/lib/gitlab/cycle_analytics/review_stage.rb
+++ b/lib/gitlab/cycle_analytics/review_stage.rb
@@ -13,12 +13,16 @@ module Gitlab
:review
end
+ def title
+ s_('CycleAnalyticsStage|Review')
+ end
+
def legend
- "Related Merged Requests"
+ _("Related Merged Requests")
end
def description
- "Time between merge request creation and merge/close"
+ _("Time between merge request creation and merge/close")
end
end
end
diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb
index eae0e8ee7b0..d5684bb9201 100644
--- a/lib/gitlab/cycle_analytics/staging_stage.rb
+++ b/lib/gitlab/cycle_analytics/staging_stage.rb
@@ -14,12 +14,16 @@ module Gitlab
:staging
end
+ def title
+ s_('CycleAnalyticsStage|Staging')
+ end
+
def legend
- "Related Deployed Jobs"
+ _("Related Deployed Jobs")
end
def description
- "From merge request merge until deploy to production"
+ _("From merge request merge until deploy to production")
end
end
end
diff --git a/lib/gitlab/cycle_analytics/summary/base.rb b/lib/gitlab/cycle_analytics/summary/base.rb
index 43fa3795e5c..a917ddccac7 100644
--- a/lib/gitlab/cycle_analytics/summary/base.rb
+++ b/lib/gitlab/cycle_analytics/summary/base.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def title
- self.class.name.demodulize
+ raise NotImplementedError.new("Expected #{self.name} to implement title")
end
def value
diff --git a/lib/gitlab/cycle_analytics/summary/commit.rb b/lib/gitlab/cycle_analytics/summary/commit.rb
index 7b8faa4d854..bea78862757 100644
--- a/lib/gitlab/cycle_analytics/summary/commit.rb
+++ b/lib/gitlab/cycle_analytics/summary/commit.rb
@@ -2,6 +2,10 @@ module Gitlab
module CycleAnalytics
module Summary
class Commit < Base
+ def title
+ n_('Commit', 'Commits', value)
+ end
+
def value
@value ||= count_commits
end
diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb
index 06032e9200e..099d798aac6 100644
--- a/lib/gitlab/cycle_analytics/summary/deploy.rb
+++ b/lib/gitlab/cycle_analytics/summary/deploy.rb
@@ -2,6 +2,10 @@ module Gitlab
module CycleAnalytics
module Summary
class Deploy < Base
+ def title
+ n_('Deploy', 'Deploys', value)
+ end
+
def value
@value ||= @project.deployments.where("created_at > ?", @from).count
end
diff --git a/lib/gitlab/cycle_analytics/summary/issue.rb b/lib/gitlab/cycle_analytics/summary/issue.rb
index 008468f24b9..9bbf7a2685f 100644
--- a/lib/gitlab/cycle_analytics/summary/issue.rb
+++ b/lib/gitlab/cycle_analytics/summary/issue.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def title
- 'New Issue'
+ n_('New Issue', 'New Issues', value)
end
def value
diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb
index de9a36d34f1..2b5f72bef89 100644
--- a/lib/gitlab/cycle_analytics/test_stage.rb
+++ b/lib/gitlab/cycle_analytics/test_stage.rb
@@ -13,12 +13,16 @@ module Gitlab
:test
end
+ def title
+ s_('CycleAnalyticsStage|Test')
+ end
+
def legend
- "Related Jobs"
+ _("Related Jobs")
end
def description
- "Total test time for all commits/merges"
+ _("Total test time for all commits/merges")
end
def stage_query
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
new file mode 100644
index 00000000000..3411516319f
--- /dev/null
+++ b/lib/gitlab/i18n.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module I18n
+ extend self
+
+ AVAILABLE_LANGUAGES = {
+ 'en' => 'English',
+ 'es' => 'Español',
+ 'de' => 'Deutsch'
+ }.freeze
+
+ def available_locales
+ AVAILABLE_LANGUAGES.keys
+ end
+
+ def set_locale(current_user)
+ requested_locale = current_user&.preferred_language || ::I18n.default_locale
+ locale = FastGettext.set_locale(requested_locale)
+ ::I18n.locale = locale
+ end
+
+ def reset_locale
+ FastGettext.set_locale(::I18n.default_locale)
+ ::I18n.locale = ::I18n.default_locale
+ end
+ end
+end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
new file mode 100644
index 00000000000..0aa21a4bd13
--- /dev/null
+++ b/lib/tasks/gettext.rake
@@ -0,0 +1,14 @@
+require "gettext_i18n_rails/tasks"
+
+namespace :gettext do
+ # Customize list of translatable files
+ # See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files
+ def files_to_translate
+ folders = %W(app lib config #{locale_path}).join(',')
+ exts = %w(rb erb haml slim rhtml js jsx vue coffee handlebars hbs mustache).join(',')
+
+ Dir.glob(
+ "{#{folders}}/**/*.{#{exts}}"
+ )
+ end
+end
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
new file mode 100644
index 00000000000..b804dc0436f
--- /dev/null
+++ b/locale/de/gitlab.po
@@ -0,0 +1,207 @@
+# German translations for gitlab package.
+# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2017-04-12 22:37-0500\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"\n"
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Median"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/locale/de/gitlab.po.time_stamp b/locale/de/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/de/gitlab.po.time_stamp
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
new file mode 100644
index 00000000000..a43bafbbe28
--- /dev/null
+++ b/locale/en/gitlab.po
@@ -0,0 +1,207 @@
+# English translations for gitlab package.
+# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2017-04-12 22:36-0500\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: English\n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"\n"
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Median"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/locale/en/gitlab.po.time_stamp b/locale/en/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/en/gitlab.po.time_stamp
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
new file mode 100644
index 00000000000..c14ddd3b94c
--- /dev/null
+++ b/locale/es/gitlab.po
@@ -0,0 +1,208 @@
+# Spanish translations for gitlab package.
+# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2017-05-04 19:24-0500\n"
+"Language-Team: Spanish\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"Last-Translator: \n"
+"X-Generator: Poedit 2.0.1\n"
+
+msgid "ByAuthor|by"
+msgstr "por"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Cambio"
+msgstr[1] "Cambios"
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr "Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Código"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Incidencia"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Planificación"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Producción"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Revisión"
+
+#, fuzzy
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Puesta en escena"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Pruebas"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Despliegue"
+msgstr[1] "Despliegues"
+
+msgid "FirstPushedBy|First"
+msgstr "Primer"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "enviado por"
+
+msgid "From issue creation until deploy to production"
+msgstr "Desde la creación de la incidencia hasta el despliegue a producción"
+
+msgid "From merge request merge until deploy to production"
+msgstr "Desde la integración de la solicitud de fusión hasta el despliegue a producción"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Introducción a Cycle Analytics"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "Último %d día"
+msgstr[1] "Últimos %d días"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limitado a mostrar máximo %d evento"
+msgstr[1] "Limitado a mostrar máximo %d eventos"
+
+msgid "Median"
+msgstr "Mediana"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nueva incidencia"
+msgstr[1] "Nuevas incidencias"
+
+msgid "Not available"
+msgstr "No disponible"
+
+msgid "Not enough data"
+msgstr "No hay suficientes datos"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Abierto"
+
+msgid "Pipeline Health"
+msgstr "Estado del Pipeline"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Etapa"
+
+msgid "Read more"
+msgstr "Leer más"
+
+msgid "Related Commits"
+msgstr "Cambios Relacionados"
+
+msgid "Related Deployed Jobs"
+msgstr "Trabajos Desplegados Relacionados"
+
+msgid "Related Issues"
+msgstr "Incidencias Relacionadas"
+
+msgid "Related Jobs"
+msgstr "Trabajos Relacionados"
+
+msgid "Related Merge Requests"
+msgstr "Solicitudes de fusión Relacionadas"
+
+msgid "Related Merged Requests"
+msgstr "Solicitudes de fusión Relacionadas"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Mostrando %d evento"
+msgstr[1] "Mostrando %d eventos"
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr "La etapa de codificación muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquí una vez creada tu primera solicitud de fusión."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "La colección de eventos agregados a los datos recopilados para esa etapa."
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr "La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."
+
+msgid "The phase of the development lifecycle."
+msgstr "La etapa del ciclo de vida de desarrollo."
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr "La etapa de planificación muestra el tiempo desde el paso anterior hasta el envío de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envíe el primer cambio."
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr "La etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr "La etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr "La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr "La etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr "El tiempo utilizado por cada entrada de datos obtenido por esa etapa."
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr "El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."
+
+msgid "Time before an issue gets scheduled"
+msgstr "Tiempo antes de que una incidencia sea programada"
+
+msgid "Time before an issue starts implementation"
+msgstr "Tiempo antes de que empieze la implementación de una incidencia"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"
+
+msgid "Time until first merge request"
+msgstr "Tiempo hasta la primera solicitud de fusión"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "hr"
+msgstr[1] "hrs"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "mins"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Tiempo Total"
+
+msgid "Total test time for all commits/merges"
+msgstr "Tiempo total de pruebas para todos los cambios o integraciones"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador."
+
+msgid "We don't have enough data to show this stage."
+msgstr "No hay suficientes datos para mostrar en esta etapa."
+
+msgid "You need permission."
+msgstr "Necesitas permisos."
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "día"
+msgstr[1] "días"
diff --git a/locale/es/gitlab.po.time_stamp b/locale/es/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/es/gitlab.po.time_stamp
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
new file mode 100644
index 00000000000..3967d40ea9e
--- /dev/null
+++ b/locale/gitlab.pot
@@ -0,0 +1,208 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-05-04 19:24-0500\n"
+"PO-Revision-Date: 2017-05-04 19:24-0500\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Median"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
+msgstr ""
+
+msgid "The phase of the development lifecycle."
+msgstr ""
+
+msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
+msgstr ""
+
+msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
+msgstr ""
+
+msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
+msgstr ""
+
+msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
+msgstr ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/package.json b/package.json
index 9ed5e1a7475..63b2c190269 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,9 @@
"dropzone": "^4.2.0",
"emoji-unicode-version": "^0.2.1",
"eslint-plugin-html": "^2.0.1",
+ "exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
+ "jed": "^1.1.1",
"jquery": "^2.2.1",
"jquery-ujs": "^1.2.1",
"js-cookie": "^2.1.3",
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index b93275c330b..7c9d522273b 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -62,6 +62,25 @@ feature 'Cycle Analytics', feature: true, js: true do
expect_issue_to_be_present
end
end
+
+ context "when my preferred language is Spanish" do
+ before do
+ user.update_attribute(:preferred_language, 'es')
+
+ project.team << [user, :master]
+ login_as(user)
+ visit namespace_project_cycle_analytics_path(project.namespace, project)
+ wait_for_ajax
+ end
+
+ it 'shows the content in Spanish' do
+ expect(page).to have_content('Estado del Pipeline')
+ end
+
+ it 'resets the language to English' do
+ expect(I18n.locale).to eq(:en)
+ end
+ end
end
context "as a guest" do
diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
index 50000c5a5f5..2fb9eb0ca85 100644
--- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
import limitWarningComp from '~/cycle_analytics/components/limit_warning_component';
+Vue.use(Translate);
+
describe('Limit warning component', () => {
let component;
let LimitWarningComponent;
diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js
new file mode 100644
index 00000000000..cbb3cbdff46
--- /dev/null
+++ b/spec/javascripts/vue_shared/translate_spec.js
@@ -0,0 +1,90 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+
+Vue.use(Translate);
+
+describe('Vue translate filter', () => {
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+
+ document.body.appendChild(el);
+ });
+
+ it('translate single text', (done) => {
+ const comp = new Vue({
+ el,
+ template: `
+ <span>
+ {{ __('testing') }}
+ </span>
+ `,
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ comp.$el.textContent.trim(),
+ ).toBe('testing');
+
+ done();
+ });
+ });
+
+ it('translate plural text with single count', (done) => {
+ const comp = new Vue({
+ el,
+ template: `
+ <span>
+ {{ n__('%d day', '%d days', 1) }}
+ </span>
+ `,
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ comp.$el.textContent.trim(),
+ ).toBe('1 day');
+
+ done();
+ });
+ });
+
+ it('translate plural text with multiple count', (done) => {
+ const comp = new Vue({
+ el,
+ template: `
+ <span>
+ {{ n__('%d day', '%d days', 2) }}
+ </span>
+ `,
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ comp.$el.textContent.trim(),
+ ).toBe('2 days');
+
+ done();
+ });
+ });
+
+ it('translate plural without replacing any text', (done) => {
+ const comp = new Vue({
+ el,
+ template: `
+ <span>
+ {{ n__('day', 'days', 2) }}
+ </span>
+ `,
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ comp.$el.textContent.trim(),
+ ).toBe('days');
+
+ done();
+ });
+ });
+});
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
new file mode 100644
index 00000000000..52f2614d5ca
--- /dev/null
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+module Gitlab
+ describe I18n, lib: true do
+ let(:user) { create(:user, preferred_language: 'es') }
+
+ describe '.set_locale' do
+ it 'sets the locale based on current user preferred language' do
+ Gitlab::I18n.set_locale(user)
+
+ expect(FastGettext.locale).to eq('es')
+ expect(::I18n.locale).to eq(:es)
+ end
+ end
+
+ describe '.reset_locale' do
+ it 'resets the locale to the default language' do
+ Gitlab::I18n.set_locale(user)
+
+ Gitlab::I18n.reset_locale
+
+ expect(FastGettext.locale).to eq('en')
+ expect(::I18n.locale).to eq(:en)
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1c2df4c9d97..13179174956 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1663,4 +1663,12 @@ describe User, models: true do
expect(User.active.count).to eq(1)
end
end
+
+ describe 'preferred language' do
+ it 'is English by default' do
+ user = create(:user)
+
+ expect(user.preferred_language).to eq('en')
+ end
+ end
end
diff --git a/yarn.lock b/yarn.lock
index fdef0665d15..8c72f36af3a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2159,6 +2159,13 @@ expand-range@^1.8.1:
dependencies:
fill-range "^2.1.0"
+exports-loader@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886"
+ dependencies:
+ loader-utils "^1.0.2"
+ source-map "0.5.x"
+
express@^4.13.3, express@^4.14.1:
version "4.14.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33"
@@ -3102,6 +3109,10 @@ jasmine-jquery@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/jasmine-jquery/-/jasmine-jquery-2.1.1.tgz#d4095e646944a26763235769ab018d9f30f0d47b"
+jed@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
+
jodid25519@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
@@ -5118,6 +5129,10 @@ source-map-support@^0.4.2:
dependencies:
source-map "^0.5.3"
+source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+
source-map@^0.1.41:
version "0.1.43"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
@@ -5130,10 +5145,6 @@ source-map@^0.4.4:
dependencies:
amdefine ">=0.0.4"
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-
source-map@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"