diff --git a/.eslintignore b/.eslintignore index c742b08c00540c1f31a99d296d17214a9e2e9bbb..1605e483e9e6387642a27955fd97013226f23212 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 bb818213de178a19e305d19da49e4a1c9dc98e26..0fb97ffb98ed7fb391bddd92495a0ff9127bd1b7 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 f54a1f500fd150407034f859a7b6ef2311df6568..9426a55861283e6d10641edcf342a2f9bbb0ca64 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 b822a325861b8c0d9218691d733e2b84e2b504f4..c7e3f9935dad28c1d8b5b1195e259bfe0e97eadc 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 abe48572347b07332cedb2eba71c1ff8c215827b..8d3d34f836f5e078eea0d2621ab84e8b32d37528 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 80bd2df6f42eaf1fb91c6f72d4ed4129002a8b99..0d9ad197abf18df85f21b52104ee6f8ba7734378 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> · <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 20a43798fbedc752ca528f5523cba5dd74006867..ad285874643fd879ed4715bfb287067b81301b68 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> · <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 f33cac3da8248131689a98764d158f1f47c19c46..222084deee94c360cf7b60789000afe36ce58045 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 657f538537433799d46416188dcbade6875f3e85..a14ebc3ece9429bdb76a6371ae711f68bc94797a 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> · <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 8a801300647aac189d99c940f971bb771890b44b..1a5bf9bc0b5118955dce4cc33c9f06e1995f556d 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> · <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 4a28637958848ce2fff20c3c63139226b90e489a..b1e9362434f1f6d49fdd719a9f840f69182124e9 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 77edcb7627323eaa7f22b02b6fe969b095a10490..d5e6167b2a82fdb4db363837d8e391e052202241 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 48cab437e02e679880300d2e474d8457ae6c02b0..c8e53cb554eb2bf7f50a861896a6ada312cf4ef2 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 681d6eef565770152c1c17ad065bdf5b60cc9cef..6504d7db2f2ae7469a8a1ea4bab6e5d2012a28d4 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 6536a8fd7fa286a657afeebd765e8e05fde1f835..50bd394e90e0a72c44b482db2bec1c81e9622a9d 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 0000000000000000000000000000000000000000..e96090da80e41dec16f16090354358f9ea39210c --- /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 0000000000000000000000000000000000000000..ade9b667b3c0f3e05300566d17d67163a6106005 --- /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 0000000000000000000000000000000000000000..3dafa21f2359a379f680543b7bfae09d23cdd71d --- /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 0000000000000000000000000000000000000000..7ba676d6d20aa0d568b29f10d98c5d76317b0d50 --- /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 0000000000000000000000000000000000000000..f83c4b00761d2ed17c9ad2ab6b9259ce83a253cd --- /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 403724cd68a849353addc9d7a76f61383727c66f..52bbb753af3372e3b59faecc07a3203aa5a2955d 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 e48f0963ef455f5f2ef50ac4c6bc4ef2370e30b9..d2c13da691741c80b00d2cd94c40c5df4823e808 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 987b95e89b9e6ead5657ccdddd07a96888d155a7..57e23cea00eb5776cfd4a4a616dd7b561953054a 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 2b7ebe6c1a71fb545b57bf3931850323d0ef57d0..43c5fdc038d4761460305e5b1c88ec02052502f5 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 69bf693de8d8e42e7b1fee460b8a3d2c4cf13347..564612202b5ec527c2b81e11182b11d6f2c93364 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 91803ec07f5ffd2cd2d5a156f4df237068b93d05..9c37afd53e1ac5972757443b531f5f33047848ec 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 36543edc040f91e562494ecea3a5a441a7279b2d..dc926a615c76b213ca03c37765e7a6785b0ddbd0 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 c74b3249a13057c85d8977d5c64922a58493ca7f..4a1438aa68e92dba2f15829a2dce5eb9ff69922f 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -72,6 +72,11 @@ = f.label :public_email, class: "label-light" = 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" diff --git a/app/views/projects/cycle_analytics/_empty_stage.html.haml b/app/views/projects/cycle_analytics/_empty_stage.html.haml index c3f95860e92375b46db2a01347c642a36869df2d..cdad0bc72318883804cecd8dc959ece22d26b99c 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 0ffc79b318122803db8239817de6cab43108845d..c3eda398234225210c056f73e43a98174b653e59 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 dd3fa814716ea390509549d3ca1c9120c1aa06c9..819f29d3ca5947211440d546fa8fa4548b9d835d 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 0000000000000000000000000000000000000000..d304fbecf908868ca134e51bca5c8a526829b7f0 --- /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 f2ecc4ce77c7e8e033844bbeb76a309c01162732..32ad239364876a8b5bdefb39877f515d17d121f4 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 0000000000000000000000000000000000000000..bea85b43b9547b38b6dc60067afab11689c25a12 --- /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 0000000000000000000000000000000000000000..69118f464caa03d0180bcde7ec2243336e425176 --- /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 0000000000000000000000000000000000000000..533663a270495b1d9b21ecc4f9aa9efef9dea632 --- /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 0000000000000000000000000000000000000000..87e79beee746043eaaf080cf53f2ccd3f61f55ac --- /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 239bb5ec43620d2132b116b12ff8392ade6467c9..3abdb6c2d76cdd18280c4a215c9f5bff2a2fc0fe 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 0000000000000000000000000000000000000000..92f1d6f243633109d19ca76f3152230ed4bd4fd4 --- /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 5d3e22d612304ba651aae25fd9f86757b257753a..132549ab34cca793ca80056e8155ce3f33df67ee 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 1bf20f76ad6a40f5a0d476461af4b51b7e19f25b..302ccd3e5327d7db9750863fee3f55bad6796b24 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 559e3939da6416139a922c6580a6cf439d0aa12b..cac31ea8cff4205040e3f22a5beb206395af78f3 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 1e52b6614a1520412f0215b0e3d2534f6b7bab2c..5f9dc9a430376713216cf22589f4de4f8eaa98e9 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 213994988a5a5f24c42af5e720274f9de9affb2d..7b03811efb21e0a1454fa7eeb2ba9374c60f9b5f 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 45d51d30ccc6bc8d14c18ca1aa68c9af83a495a7..1a0afb56b4fb042a1af8d9e9e195552a4d9dec7d 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 9f387a0294533ba19b0520dabbbad0b1560c3a8f..0fa8a65cb99112b08dbe4e0fad9c61753ca42987 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 80e5b538c6cec1cd4e4b01dda9bd42529914e928..cfbbdc43fd93c65e98c9a29869f93a5ffeebf118 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 eae0e8ee7b06740d7777671deaeea13482c781ef..d5684bb920178d6082e76ae2cceadc62d06431b6 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 43fa3795e5ca98d7669202aaaee9c11829ad5399..a917ddccac732526fe555876265b3a1bb0c59056 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 7b8faa4d854ed8e3d5b7324f60b9835f7620c7a7..bea78862757183f0c668cba08a8ec2f395a496fc 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 06032e9200edfdbcfb1f4c5574baacf0c849902b..099d798aac691f1e6f1ae2de88ef2fd122d80570 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 008468f24b90aedfd69b6a2c890ed2402bbf3c41..9bbf7a2685f52b90891ce8f682488c01659899c3 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 de9a36d34f176782571f03c6008ab835d30ec448..2b5f72bef8930d84cf4ef3ad94b4104cac612911 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 0000000000000000000000000000000000000000..3411516319f8825c5464209543e41caeb09d7145 --- /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 0000000000000000000000000000000000000000..0aa21a4bd13ba077244c4917db4f7b80bef238ab --- /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 0000000000000000000000000000000000000000..b804dc0436f4f5bc2d7f4f068f488c85ffa80ef8 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po new file mode 100644 index 0000000000000000000000000000000000000000..a43bafbbe2872f5c5797f290f3824c0660f36b4e --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po new file mode 100644 index 0000000000000000000000000000000000000000..c14ddd3b94c6c2e4fc143f855d5fe6d2e1e4412f --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/locale/gitlab.pot b/locale/gitlab.pot new file mode 100644 index 0000000000000000000000000000000000000000..3967d40ea9e50b4da3ccd915b548517d5ca8a87b --- /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 9ed5e1a7475ea4a9362fa62934ac9c578e0879c6..63b2c190269e105e5ae1bf79efaa6ac067b14f47 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 b93275c330bbc2e9a143c0f5bd0fe98a1ade0f25..7c9d522273beaed0ad9ecbf601dac0197d3a9bff 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 50000c5a5f5606bf6385ef564d4ca23e0ac0c0be..2fb9eb0ca856194f56e58c6ec7af110a2454c96e 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 0000000000000000000000000000000000000000..cbb3cbdff4675a94e8fdfed4bb56372982edee7d --- /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 0000000000000000000000000000000000000000..52f2614d5cab6d08b5a2194142d7656a4c4be491 --- /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 1c2df4c9d97415b9d74785c7206867e9e1e86da9..131791749564983f4d03a12f4bdc102588eae231 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 fdef0665d158a923aea5fe96e4eed5ea362fbf0b..8c72f36af3a139a7fe949828c8a18fbfc60a9d41 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"