From 32c74c31448cb92a1b17fe39f31414fdc0dc5c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 23 May 2017 20:31:09 +0800 Subject: added Simplified Chinese to I18N --- app/assets/javascripts/locale/zh/app.js | 203 +++++++++++++++++++++++++++++++ lib/gitlab/i18n.rb | 3 +- locale/zh/gitlab.po | 207 ++++++++++++++++++++++++++++++++ locale/zh/gitlab.po.time_stamp | 0 4 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh/app.js create mode 100644 locale/zh/gitlab.po create mode 100644 locale/zh/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js new file mode 100644 index 00000000000..49a0a10b2e1 --- /dev/null +++ b/app/assets/javascripts/locale/zh/app.js @@ -0,0 +1,203 @@ +var locales = locales || {}; +locales['zh'] = { + "domain": "app", + "locale_data": { + "app": { + "": { + "Project-Id-Version": "gitlab 1.0.0", + "Report-Msgid-Bugs-To": "", + "PO-Revision-Date": "2017-05-23 17:36-0800", + "Last-Translator": "htve ", + "Language-Team": "Simplified Chinese", + "Language": "zh", + "MIME-Version": "1.0", + "Content-Type": "text/plain; charset=UTF-8", + "Content-Transfer-Encoding": "8bit", + "Plural-Forms": "nplurals=2; plural=n != 1;", + "lang": "zh", + "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": [ + "最后%d天", + "最后%d天" + ], + "Limited to showing %d event at most": [ + "最多显示%d个事件", + "最多显示%d个事件" + ], + "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": [ + "显示%d个事件", + "显示%d个事件" + ], + "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.": [ + "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + ], + "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.": [ + "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + ], + "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.": [ + "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 3411516319f..55d11af3c8a 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -5,7 +5,8 @@ module Gitlab AVAILABLE_LANGUAGES = { 'en' => 'English', 'es' => 'Español', - 'de' => 'Deutsch' + 'de' => 'Deutsch', + 'zh' => '简体中文' }.freeze def available_locales diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po new file mode 100644 index 00000000000..3e8a8190ff5 --- /dev/null +++ b/locale/zh/gitlab.po @@ -0,0 +1,207 @@ +# Simplified Chinese translations for gitlab package. +# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# htve , 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2017-05-23 15:35-0800\n" +"Last-Translator: htve \n" +"Language-Team: Simplified Chinese\n" +"Language: zh\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] "最后%d天" +msgstr[1] "最后%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多显示%d个事件" +msgstr[1] "最多显示%d个事件" + +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] "显示%d个事件" +msgstr[1] "显示%d个事件" + +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 "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + +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 "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + +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 "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5 + 7)/ 2 = 6。" + +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/zh/gitlab.po.time_stamp b/locale/zh/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From e01e9321a17b69100cdbda1b07f8482b73b017c5 Mon Sep 17 00:00:00 2001 From: htve Date: Tue, 23 May 2017 12:42:37 +0000 Subject: Update i18n.rb style --- lib/gitlab/i18n.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 55d11af3c8a..347313d71b6 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,7 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh' => '简体中文' + 'zh' => '简体中文' }.freeze def available_locales -- cgit v1.2.1 From eb1da51e11dfa48ce4c028123def013a569d020b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 26 May 2017 13:15:32 +0800 Subject: optimize part of the translation --- app/assets/javascripts/locale/zh/app.js | 56 ++++++++++++++++---------------- locale/zh/gitlab.po | 57 +++++++++++++++++---------------- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js index 49a0a10b2e1..e266b91cc5b 100644 --- a/app/assets/javascripts/locale/zh/app.js +++ b/app/assets/javascripts/locale/zh/app.js @@ -6,7 +6,7 @@ locales['zh'] = { "": { "Project-Id-Version": "gitlab 1.0.0", "Report-Msgid-Bugs-To": "", - "PO-Revision-Date": "2017-05-23 17:36-0800", + "PO-Revision-Date": "2017-05-26 13:08+0800", "Last-Translator": "htve ", "Language-Team": "Simplified Chinese", "Language": "zh", @@ -26,13 +26,13 @@ locales['zh'] = { "提交" ], "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": [ "计划" @@ -44,7 +44,7 @@ locales['zh'] = { "评审" ], "CycleAnalyticsStage|Staging": [ - "排期" + "预发布" ], "CycleAnalyticsStage|Test": [ "测试" @@ -57,16 +57,16 @@ locales['zh'] = { "首个推送" ], "FirstPushedBy|pushed by": [ - "作者:" + "推送者:" ], "From issue creation until deploy to production": [ - "从问题创建到部署到生产" + "从创建议题到部署到生产环境" ], "From merge request merge until deploy to production": [ "从合并请求的合并到部署至生产环境" ], "Introducing Cycle Analytics": [ - "引入周期分析" + "周期分析简介" ], "Last %d day": [ "最后%d天", @@ -80,7 +80,7 @@ locales['zh'] = { "平均数" ], "New Issue": [ - "新问题", + "新议题", "新问题" ], "Not available": [ @@ -93,13 +93,13 @@ locales['zh'] = { "开始于" ], "Pipeline Health": [ - "流水线健康" + "流水线健康指标" ], "ProjectLifecycle|Stage": [ "项目生命周期" ], "Read more": [ - "阅读更多" + "了解更多" ], "Related Commits": [ "相关的提交" @@ -108,7 +108,7 @@ locales['zh'] = { "相关的部署作业" ], "Related Issues": [ - "相关的问题" + "相关的议题" ], "Related Jobs": [ "相关的作业" @@ -124,49 +124,49 @@ locales['zh'] = { "显示%d个事件" ], "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.": [ - "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + "预发布阶段概述了合并请求的合并到部署代码到生产环境的时间。首次部署到生产环境后,数据将自动添加到此处。" ], "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.": [ - "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" ], "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.": [ - "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5+7)/2 = 6。" + "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(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": [ "小时", @@ -186,7 +186,7 @@ locales['zh'] = { "所有提交和合并的总测试时间" ], "Want to see the data? Please ask an administrator for access.": [ - "想查看数据?请向管理员查询。" + "权限不足。如需查看相关数据,请向管理员申请权限。" ], "We don't have enough data to show this stage.": [ "我们没有足够的数据显示这个阶段。" diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po index 3e8a8190ff5..be61691625a 100644 --- a/locale/zh/gitlab.po +++ b/locale/zh/gitlab.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-23 15:35-0800\n" +"PO-Revision-Date: 2017-05-26 13:08+0800\n" "Last-Translator: htve \n" "Language-Team: Simplified Chinese\n" "Language: zh\n" @@ -26,13 +26,14 @@ 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 "周期分析概述了您的项目中从创意到生产所需的时间。" +msgstr "周期分析概述了项目从想法到产品实现的各阶段所需的时间。" msgid "CycleAnalyticsStage|Code" -msgstr "代码" +msgstr "编码" +# '议题' sounds good, but most of the software on the market will 'issue' translated into '问题' msgid "CycleAnalyticsStage|Issue" -msgstr "问题" +msgstr "议题" msgid "CycleAnalyticsStage|Plan" msgstr "计划" @@ -44,7 +45,7 @@ msgid "CycleAnalyticsStage|Review" msgstr "评审" msgid "CycleAnalyticsStage|Staging" -msgstr "排期" +msgstr "预发布" msgid "CycleAnalyticsStage|Test" msgstr "测试" @@ -58,16 +59,16 @@ msgid "FirstPushedBy|First" msgstr "首个推送" msgid "FirstPushedBy|pushed by" -msgstr "作者:" +msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "从问题创建到部署到生产" +msgstr "从创建议题到部署到生产环境" msgid "From merge request merge until deploy to production" msgstr "从合并请求的合并到部署至生产环境" msgid "Introducing Cycle Analytics" -msgstr "引入周期分析" +msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" @@ -84,7 +85,7 @@ msgstr "平均数" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "新问题" +msgstr[0] "新议题" msgstr[1] "新问题" msgid "Not available" @@ -97,13 +98,13 @@ msgid "OpenedNDaysAgo|Opened" msgstr "开始于" msgid "Pipeline Health" -msgstr "流水线健康" +msgstr "流水线健康指标" msgid "ProjectLifecycle|Stage" msgstr "项目生命周期" msgid "Read more" -msgstr "阅读更多" +msgstr "了解更多" msgid "Related Commits" msgstr "相关的提交" @@ -112,7 +113,7 @@ msgid "Related Deployed Jobs" msgstr "相关的部署作业" msgid "Related Issues" -msgstr "相关的问题" +msgstr "相关的议题" msgid "Related Jobs" msgstr "相关的作业" @@ -129,49 +130,49 @@ msgstr[0] "显示%d个事件" msgstr[1] "显示%d个事件" 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 "编码阶段显示从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" +msgstr "编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" msgid "The collection of events added to the data gathered for that stage." -msgstr "将收集的事件添加到为该阶段收集的数据中。" +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 "问题阶段显示了从创建问题到将问题分配给里程碑所需的时间,或将问题添加到问题委员会的列表中。开始创建问题以查看此阶段的数据" +msgstr "议题阶段概述了从创建议题到将议题分配给里程碑或将议题添加到议题看板列表中所需的时间。开始创建议题以查看此阶段的数据。" msgid "The phase of the development lifecycle." -msgstr "开发生命周期中的各个阶段。" +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 "规划阶段显示了从上一步到推送第一次提交的时间。一旦你推出第一次提交,这个时候就会自动添加。" +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 "生产阶段显示创建问题和将代码部署到生产之间的总时间。一旦完成完整的想法到生产周期,数据就会自动添加。" +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 "审查阶段显示从合并请求到合并的时间。合并您的第一个合并请求后,数据将自动添加。" +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 "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" +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 "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" +msgstr "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由该阶段收集的每个数据条目所用的时间。" +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 "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5 + 7)/ 2 = 6。" +msgstr "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "发布问题之前的时间" +msgstr "创建议题之前的时间" msgid "Time before an issue starts implementation" -msgstr "问题开始实施之前的时间" +msgstr "从创建议题到开始编码的时间" msgid "Time between merge request creation and merge/close" -msgstr "合并请求创建到合并或关闭之间的时间" +msgstr "从创建合并请求到合并或关闭合并请求的时间" msgid "Time until first merge request" -msgstr "时间到第一个合并请求" +msgstr "创建第一个合并请求之前的时间" msgid "Time|hr" msgid_plural "Time|hrs" @@ -193,7 +194,7 @@ msgid "Total test time for all commits/merges" msgstr "所有提交和合并的总测试时间" msgid "Want to see the data? Please ask an administrator for access." -msgstr "想查看数据?请向管理员查询。" +msgstr "权限不足。如需查看相关数据,请向管理员申请权限。" msgid "We don't have enough data to show this stage." msgstr "我们没有足够的数据显示这个阶段。" -- cgit v1.2.1 From 205462fa0e66b1b4fc0d7225f42bd167f5e41667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 26 May 2017 16:42:48 +0800 Subject: fix plural form of translation --- app/assets/javascripts/locale/zh/app.js | 24 ++++------- locale/zh/gitlab.po | 75 ++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js index e266b91cc5b..18b834ef32e 100644 --- a/app/assets/javascripts/locale/zh/app.js +++ b/app/assets/javascripts/locale/zh/app.js @@ -6,23 +6,23 @@ locales['zh'] = { "": { "Project-Id-Version": "gitlab 1.0.0", "Report-Msgid-Bugs-To": "", - "PO-Revision-Date": "2017-05-26 13:08+0800", - "Last-Translator": "htve ", - "Language-Team": "Simplified Chinese", - "Language": "zh", + "POT-Creation-Date": "2017-05-04 19:24-0500", + "PO-Revision-Date": "2017-05-04 19:24-0500", + "Last-Translator": "HuangTao , 2017", + "Language-Team": "Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", - "Plural-Forms": "nplurals=2; plural=n != 1;", + "Language": "zh_CN", + "Plural-Forms": "nplurals=1; plural=0;", "lang": "zh", "domain": "app", - "plural_forms": "nplurals=2; plural=n != 1;" + "plural_forms": "nplurals=1; plural=0;" }, "ByAuthor|by": [ "作者:" ], "Commit": [ - "提交", "提交" ], "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.": [ @@ -50,7 +50,6 @@ locales['zh'] = { "测试" ], "Deploy": [ - "部署", "部署" ], "FirstPushedBy|First": [ @@ -69,19 +68,16 @@ locales['zh'] = { "周期分析简介" ], "Last %d day": [ - "最后%d天", "最后%d天" ], "Limited to showing %d event at most": [ - "最多显示%d个事件", "最多显示%d个事件" ], "Median": [ "平均数" ], "New Issue": [ - "新议题", - "新问题" + "新议题" ], "Not available": [ "不可用" @@ -120,7 +116,6 @@ locales['zh'] = { "相关已合并的合并请求" ], "Showing %d event": [ - "显示%d个事件", "显示%d个事件" ], "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.": [ @@ -169,11 +164,9 @@ locales['zh'] = { "创建第一个合并请求之前的时间" ], "Time|hr": [ - "小时", "小时" ], "Time|min": [ - "分钟", "分钟" ], "Time|s": [ @@ -195,7 +188,6 @@ locales['zh'] = { "您需要相关的权限。" ], "day": [ - "天", "天" ] } diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po index be61691625a..72c9fe71d15 100644 --- a/locale/zh/gitlab.po +++ b/locale/zh/gitlab.po @@ -1,21 +1,22 @@ -# Simplified Chinese translations for gitlab package. -# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gitlab package. -# htve , 2017. -# +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-26 13:08+0800\n" -"Last-Translator: htve \n" -"Language-Team: Simplified Chinese\n" -"Language: zh\n" +"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"PO-Revision-Date: 2017-05-04 19:24-0500\n" +"Last-Translator: HuangTao , 2017\n" +"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\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" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" msgid "ByAuthor|by" msgstr "作者:" @@ -23,15 +24,15 @@ 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." +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 "编码" -# '议题' sounds good, but most of the software on the market will 'issue' translated into '问题' msgid "CycleAnalyticsStage|Issue" msgstr "议题" @@ -53,7 +54,6 @@ msgstr "测试" msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" -msgstr[1] "部署" msgid "FirstPushedBy|First" msgstr "首个推送" @@ -73,12 +73,10 @@ msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最后%d天" -msgstr[1] "最后%d天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "最多显示%d个事件" -msgstr[1] "最多显示%d个事件" msgid "Median" msgstr "平均数" @@ -86,7 +84,6 @@ msgstr "平均数" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新议题" -msgstr[1] "新问题" msgid "Not available" msgstr "不可用" @@ -127,39 +124,62 @@ msgstr "相关已合并的合并请求" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "显示%d个事件" -msgstr[1] "显示%d个事件" -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." +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." +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." +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." +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." +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." +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." +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" 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." +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 "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" @@ -177,12 +197,10 @@ 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 "秒" @@ -205,4 +223,3 @@ msgstr "您需要相关的权限。" msgid "day" msgid_plural "days" msgstr[0] "天" -msgstr[1] "天" -- cgit v1.2.1 From 58b51c9709831712e60b0a2b6b3cb6399c3cdf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:20:37 +0800 Subject: Change the language name zh to zh_CN --- app/assets/javascripts/locale/zh/app.js | 195 ------------------------- app/assets/javascripts/locale/zh_CN/app.js | 1 + lib/gitlab/i18n.rb | 2 +- locale/zh/gitlab.po | 225 ----------------------------- locale/zh/gitlab.po.time_stamp | 0 locale/zh_CN/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_CN/gitlab.po.time_stamp | 0 7 files changed, 227 insertions(+), 421 deletions(-) delete mode 100644 app/assets/javascripts/locale/zh/app.js create mode 100644 app/assets/javascripts/locale/zh_CN/app.js delete mode 100644 locale/zh/gitlab.po delete mode 100644 locale/zh/gitlab.po.time_stamp create mode 100644 locale/zh_CN/gitlab.po create mode 100644 locale/zh_CN/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js deleted file mode 100644 index 18b834ef32e..00000000000 --- a/app/assets/javascripts/locale/zh/app.js +++ /dev/null @@ -1,195 +0,0 @@ -var locales = locales || {}; -locales['zh'] = { - "domain": "app", - "locale_data": { - "app": { - "": { - "Project-Id-Version": "gitlab 1.0.0", - "Report-Msgid-Bugs-To": "", - "POT-Creation-Date": "2017-05-04 19:24-0500", - "PO-Revision-Date": "2017-05-04 19:24-0500", - "Last-Translator": "HuangTao , 2017", - "Language-Team": "Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)", - "MIME-Version": "1.0", - "Content-Type": "text/plain; charset=UTF-8", - "Content-Transfer-Encoding": "8bit", - "Language": "zh_CN", - "Plural-Forms": "nplurals=1; plural=0;", - "lang": "zh", - "domain": "app", - "plural_forms": "nplurals=1; plural=0;" - }, - "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": [ - "最后%d天" - ], - "Limited to showing %d event at most": [ - "最多显示%d个事件" - ], - "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": [ - "显示%d个事件" - ], - "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.": [ - "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" - ], - "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.": [ - "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(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/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js new file mode 100644 index 00000000000..913235ead5d --- /dev/null +++ b/app/assets/javascripts/locale/zh_CN/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后%d天"],"Limited to showing %d event at most":["最多显示%d个事件"],"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":["显示%d个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。"],"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.":["该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 347313d71b6..6fbbeb07fb2 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,7 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh' => '简体中文' + 'zh_CN' => '简体中文' }.freeze def available_locales diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po deleted file mode 100644 index 72c9fe71d15..00000000000 --- a/locale/zh/gitlab.po +++ /dev/null @@ -1,225 +0,0 @@ -# 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 , 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: HuangTao , 2017\n" -"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -msgid "ByAuthor|by" -msgstr "作者:" - -msgid "Commit" -msgid_plural "Commits" -msgstr[0] "提交" - -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] "部署" - -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] "最后%d天" - -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多显示%d个事件" - -msgid "Median" -msgstr "平均数" - -msgid "New Issue" -msgid_plural "New Issues" -msgstr[0] "新议题" - -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] "显示%d个事件" - -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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" - -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 "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" - -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] "小时" - -msgid "Time|min" -msgid_plural "Time|mins" -msgstr[0] "分钟" - -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] "天" diff --git a/locale/zh/gitlab.po.time_stamp b/locale/zh/gitlab.po.time_stamp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po new file mode 100644 index 00000000000..5fb8dbe5fdd --- /dev/null +++ b/locale/zh_CN/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最后%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多显示%d个事件" + +msgid "Median" +msgstr "中位数" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新议题" + +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] "显示%d个事件" + +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" + +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 "该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(5 + 7)/ 2 = 6。" + +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] "小时" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分钟" + +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] "天" diff --git a/locale/zh_CN/gitlab.po.time_stamp b/locale/zh_CN/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 44fc9436b25f25a565c428821775124a48265b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:22:01 +0800 Subject: Add translation zh_HK to I18N --- app/assets/javascripts/locale/zh_HK/app.js | 1 + lib/gitlab/i18n.rb | 3 +- locale/zh_HK/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_HK/gitlab.po.time_stamp | 0 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh_HK/app.js create mode 100644 locale/zh_HK/gitlab.po create mode 100644 locale/zh_HK/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js new file mode 100644 index 00000000000..721dd8efadf --- /dev/null +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 6fbbeb07fb2..16b437703a1 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,8 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh_CN' => '简体中文' + 'zh_CN' => '简体中文', + 'zh_HK' => '繁體中文(香港)' }.freeze def available_locales diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po new file mode 100644 index 00000000000..95c3fe58da8 --- /dev/null +++ b/locale/zh_HK/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_HK\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最後%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多顯示%d個事件" + +msgid "Median" +msgstr "中位數" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新議題" + +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] "顯示%d個事件" + +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" + +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" + +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] "小時" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分鐘" + +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] "天" diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 0df8ed1a2cb633c2e8d8665ee3c98c0157e49abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:23:26 +0800 Subject: Add translation zh_TW to I18N --- app/assets/javascripts/locale/zh_TW/app.js | 1 + lib/gitlab/i18n.rb | 3 +- locale/zh_TW/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_TW/gitlab.po.time_stamp | 0 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh_TW/app.js create mode 100644 locale/zh_TW/gitlab.po create mode 100644 locale/zh_TW/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js new file mode 100644 index 00000000000..09fdd332193 --- /dev/null +++ b/app/assets/javascripts/locale/zh_TW/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 16b437703a1..504a920e07d 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -7,7 +7,8 @@ module Gitlab 'es' => 'Español', 'de' => 'Deutsch', 'zh_CN' => '简体中文', - 'zh_HK' => '繁體中文(香港)' + 'zh_HK' => '繁體中文(香港)', + 'zh_TW' => '繁體中文(臺灣)' }.freeze def available_locales diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po new file mode 100644 index 00000000000..2cb9da2b5cd --- /dev/null +++ b/locale/zh_TW/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最後%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多顯示%d個事件" + +msgid "Median" +msgstr "中位數" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新議題" + +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] "顯示%d個事件" + +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" + +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" + +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] "小時" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分鐘" + +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] "天" diff --git a/locale/zh_TW/gitlab.po.time_stamp b/locale/zh_TW/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 1e20f27c6155570f4b9d1fd95186bcd6808d07e9 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Sat, 27 May 2017 03:34:55 +0000 Subject: Update i18n.rb Indent style --- lib/gitlab/i18n.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 504a920e07d..195b03622a4 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,9 +6,9 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh_CN' => '简体中文', - 'zh_HK' => '繁體中文(香港)', - 'zh_TW' => '繁體中文(臺灣)' + 'zh_CN' => '简体中文', + 'zh_HK' => '繁體中文(香港)', + 'zh_TW' => '繁體中文(臺灣)' }.freeze def available_locales -- cgit v1.2.1 From 4c237d7a1d7b80f2d99a3bcba19a4143fc6dd855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 31 May 2017 17:21:17 +0800 Subject: Optimize translation content 1. Optimize the translation of zh-TW 2. Synchronous zh-CN, zh-HK translation --- app/assets/javascripts/locale/zh_CN/app.js | 2 +- app/assets/javascripts/locale/zh_HK/app.js | 2 +- app/assets/javascripts/locale/zh_TW/app.js | 2 +- locale/zh_CN/gitlab.po | 40 ++++++++-------- locale/zh_HK/gitlab.po | 54 +++++++++++----------- locale/zh_TW/gitlab.po | 74 +++++++++++++++--------------- 6 files changed, 87 insertions(+), 87 deletions(-) diff --git a/app/assets/javascripts/locale/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js index 913235ead5d..9525bc88190 100644 --- a/app/assets/javascripts/locale/zh_CN/app.js +++ b/app/assets/javascripts/locale/zh_CN/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后%d天"],"Limited to showing %d event at most":["最多显示%d个事件"],"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":["显示%d个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。"],"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.":["该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(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 +var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后 %d 天"],"Limited to showing %d event at most":["最多显示 %d 个事件"],"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":["显示 %d 个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"],"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.":["中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (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/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 721dd8efadf..fd0bcd988c5 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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 +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後 %d 天"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"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":["顯示 %d 個事件"],"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.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"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.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (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/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js index 09fdd332193..79904d17bf6 100644 --- a/app/assets/javascripts/locale/zh_TW/app.js +++ b/app/assets/javascripts/locale/zh_TW/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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 +var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後 %d 天"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"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":["顯示 %d 個事件"],"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.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (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/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 5fb8dbe5fdd..c2d69b122e2 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -56,27 +56,27 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首个推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "从创建议题到部署到生产环境" +msgstr "从创建议题到部署至生产环境" msgid "From merge request merge until deploy to production" -msgstr "从合并请求的合并到部署至生产环境" +msgstr "从合并请求被合并后到部署至生产环境" msgid "Introducing Cycle Analytics" msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最后%d天" +msgstr[0] "最后 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多显示%d个事件" +msgstr[0] "最多显示 %d 个事件" msgid "Median" msgstr "中位数" @@ -86,7 +86,7 @@ msgid_plural "New Issues" msgstr[0] "新议题" msgid "Not available" -msgstr "不可用" +msgstr "数据不足" msgid "Not enough data" msgstr "数据不足" @@ -123,7 +123,7 @@ msgstr "相关已合并的合并请求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "显示%d个事件" +msgstr[0] "显示 %d 个事件" msgid "" "The coding stage shows the time from the first commit to creating the merge " @@ -132,13 +132,13 @@ msgid "" msgstr "编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" msgid "The collection of events added to the data gathered for that stage." -msgstr "将收集的事件添加到该阶段的相关统计数据中。" +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 "议题阶段概述了从创建议题到将议题分配给里程碑或将议题添加到议题看板列表中所需的时间。开始创建议题以查看此阶段的数据。" +msgstr "议题阶段概述了从创建议题到将议题设置里程碑或将议题添加到议题看板的时间。开始创建议题以查看此阶段的数据。" msgid "The phase of the development lifecycle." msgstr "项目生命周期中的各个阶段。" @@ -147,49 +147,49 @@ 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 "计划阶段概述了从议题到推送第一次提交的时间。当你第一次推送提交,数据将自动添加到此处。" +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 "生产阶段概述了从创建议题到将代码部署到生产环境的时间。一旦完成完整的想法到部署生产,数据将自动添加到此处。" +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 "评审阶段概述了从创建合并请求到合并的时间。在您合并第一个合并请求后,数据将自动添加到此处。" +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 "预发布阶段概述了合并请求的合并到部署代码到生产环境的时间。首次部署到生产环境后,数据将自动添加到此处。" +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" +msgstr "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由该阶段收集的每个数据条目所用的时间总和。" +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 "该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(5 + 7)/ 2 = 6。" +msgstr "中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "创建议题之前的时间" +msgstr "议题被列入日程表的时间" msgid "Time before an issue starts implementation" -msgstr "从创建议题到开始编码的时间" +msgstr "开始进行编码前的时间" msgid "Time between merge request creation and merge/close" -msgstr "从创建合并请求到合并或关闭合并请求的时间" +msgstr "从创建合并请求到被合并或关闭的时间" msgid "Time until first merge request" msgstr "创建第一个合并请求之前的时间" @@ -215,7 +215,7 @@ 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 "我们没有足够的数据显示这个阶段。" +msgstr "该阶段的数据不足,无法显示。" msgid "You need permission." msgstr "您需要相关的权限。" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 95c3fe58da8..6d56b2897fa 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -28,7 +28,7 @@ msgstr[0] "提交" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." -msgstr "周期分析概述了項目從想法到產品實現的各階段所需的時間。" +msgstr "週期分析概述了項目從想法到產品實現的各階段所需的時間。" msgid "CycleAnalyticsStage|Code" msgstr "編碼" @@ -56,7 +56,7 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首個推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" @@ -65,18 +65,18 @@ msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" -msgstr "從合並請求的合並到部署至生產環境" +msgstr "從合併請求的合併到部署至生產環境" msgid "Introducing Cycle Analytics" -msgstr "周期分析簡介" +msgstr "週期分析簡介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後%d天" +msgstr[0] "最後 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示%d個事件" +msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" @@ -98,7 +98,7 @@ msgid "Pipeline Health" msgstr "流水線健康指標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命周期" +msgstr "項目生命週期" msgid "Read more" msgstr "了解更多" @@ -116,83 +116,83 @@ msgid "Related Jobs" msgstr "相關的作業" msgid "Related Merge Requests" -msgstr "相關的合並請求" +msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合並的合並請求" +msgstr "相關已合併的合並請求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "顯示%d個事件" +msgstr[0] "顯示 %d 個事件" 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 "編碼階段概述了從第壹次提交到創建合並請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." -msgstr "將收集的事件添加到該階段的相關統計數據中。" +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 "議題階段概述了從創建議題到將議題分配給裏程碑或將議題添加到議題看板列表中所需的時間。開始創建議題以查看此階段的數據。" +msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" msgid "The phase of the development lifecycle." -msgstr "項目生命周期中的各個階段。" +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 "計劃階段概述了從議題到推送第壹次提交的時間。當妳第壹次推送提交,數據將自動添加到此處。" +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 "生產階段概述了從創建議題到將代碼部署到生產環境的時間。壹旦完成完整的想法到部署生產,數據將自動添加到此處。" +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 "評審階段概述了從創建合並請求到合並的時間。在您合並第壹個合並請求後,數據將自動添加到此處。" +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 "預發布階段概述了合並請求的合並到部署代碼到生產環境的時間。首次部署到生產環境後,數據將自動添加到此處。" +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" +msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由該階段收集的每個數據條目所用的時間總和。" +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" +msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "創建議題之前的時間" +msgstr "議題被列入日程表的時間" msgid "Time before an issue starts implementation" -msgstr "從創建議題到開始編碼的時間" +msgstr "開始進行編碼前的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合並請求到合並或關閉合並請求的時間" +msgstr "從創建合併請求到被合並或關閉的時間" msgid "Time until first merge request" -msgstr "創建第壹個合並請求之前的時間" +msgstr "創建第壹個合併請求之前的時間" msgid "Time|hr" msgid_plural "Time|hrs" @@ -209,13 +209,13 @@ msgid "Total Time" msgstr "總時間" msgid "Total test time for all commits/merges" -msgstr "所有提交和合並的總測試時間" +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 "我們沒有足夠的數據顯示這個階段。" +msgstr "該階段的數據不足,無法顯示。" msgid "You need permission." msgstr "您需要相關的權限。" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 2cb9da2b5cd..0caf35a915b 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -23,15 +23,15 @@ msgstr "作者:" msgid "Commit" msgid_plural "Commits" -msgstr[0] "提交" +msgstr[0] "送交" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." -msgstr "周期分析概述了項目從想法到產品實現的各階段所需的時間。" +msgstr "週期分析概述了你的專案從想法到產品實現,各階段所需的時間。" msgid "CycleAnalyticsStage|Code" -msgstr "編碼" +msgstr "程式開發" msgid "CycleAnalyticsStage|Issue" msgstr "議題" @@ -40,13 +40,13 @@ msgid "CycleAnalyticsStage|Plan" msgstr "計劃" msgid "CycleAnalyticsStage|Production" -msgstr "生產" +msgstr "上線" msgid "CycleAnalyticsStage|Review" -msgstr "評審" +msgstr "複閱" msgid "CycleAnalyticsStage|Staging" -msgstr "預發布" +msgstr "預備" msgid "CycleAnalyticsStage|Test" msgstr "測試" @@ -56,27 +56,27 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首個推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "從創建議題到部署到生產環境" +msgstr "從議題建立至線上部署" msgid "From merge request merge until deploy to production" -msgstr "從合並請求的合並到部署至生產環境" +msgstr "從請求被合併後至線上部署" msgid "Introducing Cycle Analytics" -msgstr "周期分析簡介" +msgstr "週期分析簡介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後%d天" +msgstr[0] "最後 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示%d個事件" +msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" @@ -86,10 +86,10 @@ msgid_plural "New Issues" msgstr[0] "新議題" msgid "Not available" -msgstr "不可用" +msgstr "無法使用" msgid "Not enough data" -msgstr "數據不足" +msgstr "資料不足" msgid "OpenedNDaysAgo|Opened" msgstr "開始於" @@ -98,13 +98,13 @@ msgid "Pipeline Health" msgstr "流水線健康指標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命周期" +msgstr "專案生命週期" msgid "Read more" msgstr "了解更多" msgid "Related Commits" -msgstr "相關的提交" +msgstr "相關的送交" msgid "Related Deployed Jobs" msgstr "相關的部署作業" @@ -116,83 +116,83 @@ msgid "Related Jobs" msgstr "相關的作業" msgid "Related Merge Requests" -msgstr "相關的合並請求" +msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合並的合並請求" +msgstr "相關已合併的請求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "顯示%d個事件" +msgstr[0] "顯示 %d 個事件" 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 "編碼階段概述了從第壹次提交到創建合並請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。" msgid "The collection of events added to the data gathered for that stage." -msgstr "將收集的事件添加到該階段的相關統計數據中。" +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 "議題階段概述了從創建議題到將議題分配給裏程碑或將議題添加到議題看板列表中所需的時間。開始創建議題以查看此階段的數據。" +msgstr "議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。" msgid "The phase of the development lifecycle." -msgstr "項目生命周期中的各個階段。" +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 "計劃階段概述了從議題到推送第壹次提交的時間。當妳第壹次推送提交,數據將自動添加到此處。" +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 "生產階段概述了從創建議題到將代碼部署到生產環境的時間。壹旦完成完整的想法到部署生產,數據將自動添加到此處。" +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 "評審階段概述了從創建合並請求到合並的時間。在您合並第壹個合並請求後,數據將自動添加到此處。" +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 "預發布階段概述了合並請求的合並到部署代碼到生產環境的時間。首次部署到生產環境後,數據將自動添加到此處。" +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" +msgstr "測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由該階段收集的每個數據條目所用的時間總和。" +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" +msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "創建議題之前的時間" +msgstr "議題被列入日程表的時間" msgid "Time before an issue starts implementation" -msgstr "從創建議題到開始編碼的時間" +msgstr "議題等待開始實作的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合並請求到合並或關閉合並請求的時間" +msgstr "合併請求被合併或是關閉的時間" msgid "Time until first merge request" -msgstr "創建第壹個合並請求之前的時間" +msgstr "第一個合併請求被建立前的時間" msgid "Time|hr" msgid_plural "Time|hrs" @@ -209,13 +209,13 @@ msgid "Total Time" msgstr "總時間" msgid "Total test time for all commits/merges" -msgstr "所有提交和合並的總測試時間" +msgstr "所有送交和合併的總測試時間" msgid "Want to see the data? Please ask an administrator for access." -msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" +msgstr "權限不足。如需查看相關資料,請向管理員申請權限。" msgid "We don't have enough data to show this stage." -msgstr "我們沒有足夠的數據顯示這個階段。" +msgstr "因該階段的資料不足而無法顯示相關資訊" msgid "You need permission." msgstr "您需要相關的權限。" -- cgit v1.2.1 From 0c1bf16d5f347da60bb84027db209ffc7b02f601 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 5 May 2017 16:59:31 +0100 Subject: Backport EE refactorings for Protected Tag EE-only functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improvements and refactorings were made while adding role based permissions for protected tags to EE. This doesn’t backport the feature, but should improve code quality and minimize divergence. --- .../protected_tags/protected_tag_dropdown.js | 4 ++-- app/assets/stylesheets/pages/projects.scss | 6 +++-- .../projects/protected_branches_controller.rb | 4 ++-- .../projects/protected_refs_controller.rb | 6 +++++ .../projects/protected_tags_controller.rb | 2 +- app/models/concerns/protected_ref.rb | 28 +++++++++++++++------- app/models/protected_branch.rb | 9 +------ app/models/protected_tag.rb | 6 +---- .../protected_tags/_create_protected_tag.html.haml | 2 +- .../projects/protected_tags/_dropdown.html.haml | 4 ++-- app/views/projects/protected_tags/_index.html.haml | 5 ++-- .../protected_tags/_protected_tag.html.haml | 2 +- .../projects/protected_tags/_tags_list.html.haml | 4 +++- app/views/projects/protected_tags/show.html.haml | 2 +- spec/lib/gitlab/import_export/all_models.yml | 2 ++ 15 files changed, 50 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js index 068e9698e1d..9d045886262 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js +++ b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js @@ -10,7 +10,7 @@ export default class ProtectedTagDropdown { this.$dropdown = options.$dropdown; this.$dropdownContainer = this.$dropdown.parent(); this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer'); - this.$protectedTag = this.$dropdownContainer.find('.create-new-protected-tag'); + this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag'); this.buildDropdown(); this.bindEvents(); @@ -73,7 +73,7 @@ export default class ProtectedTagDropdown { }; this.$dropdownContainer - .find('.create-new-protected-tag code') + .find('.js-create-new-protected-tag code') .text(tagName); } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 24ab2bedea2..4719bf826dc 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -675,14 +675,16 @@ pre.light-well { } } -.new_protected_branch { +.new_protected_branch, +.new-protected-tag { label { margin-top: 6px; font-weight: normal; } } -.create-new-protected-branch-button { +.create-new-protected-branch-button, +.create-new-protected-tag-button { @include dropdown-link; width: 100%; diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index ba24fa9acfe..d1719f12072 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -19,7 +19,7 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController def protected_ref_params params.require(:protected_branch).permit(:name, - merge_access_levels_attributes: [:access_level, :id], - push_access_levels_attributes: [:access_level, :id]) + merge_access_levels_attributes: access_level_attributes, + push_access_levels_attributes: access_level_attributes) end end diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb index 083a70968e5..b51bdf7aa78 100644 --- a/app/controllers/projects/protected_refs_controller.rb +++ b/app/controllers/projects/protected_refs_controller.rb @@ -44,4 +44,10 @@ class Projects::ProtectedRefsController < Projects::ApplicationController format.js { head :ok } end end + + protected + + def access_level_attributes + %i(access_level id) + end end diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb index c61ddf145e6..a5dbd7e46ae 100644 --- a/app/controllers/projects/protected_tags_controller.rb +++ b/app/controllers/projects/protected_tags_controller.rb @@ -18,6 +18,6 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController end def protected_ref_params - params.require(:protected_tag).permit(:name, create_access_levels_attributes: [:access_level, :id]) + params.require(:protected_tag).permit(:name, create_access_levels_attributes: access_level_attributes) end end diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 62eaec2407f..47e71c58557 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -8,32 +8,44 @@ module ProtectedRef validates :project, presence: true delegate :matching, :matches?, :wildcard?, to: :ref_matcher + end + + def commit + project.commit(self.name) + end + + class_methods do + def protected_ref_access_levels(*types) + types.each do |type| + has_many :"#{type}_access_levels", dependent: :destroy + + validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." } - def self.protected_ref_accessible_to?(ref, user, action:) + accepts_nested_attributes_for :"#{type}_access_levels", allow_destroy: true + end + end + + def protected_ref_accessible_to?(ref, user, action:) access_levels_for_ref(ref, action: action).any? do |access_level| access_level.check_access(user) end end - def self.developers_can?(action, ref) + def developers_can?(action, ref) access_levels_for_ref(ref, action: action).any? do |access_level| access_level.access_level == Gitlab::Access::DEVELOPER end end - def self.access_levels_for_ref(ref, action:) + def access_levels_for_ref(ref, action:) self.matching(ref).map(&:"#{action}_access_levels").flatten end - def self.matching(ref_name, protected_refs: nil) + def matching(ref_name, protected_refs: nil) ProtectedRefMatcher.matching(self, ref_name, protected_refs: protected_refs) end end - def commit - project.commit(self.name) - end - private def ref_matcher diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 28b7d5ad072..5f0d0802ac9 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -2,14 +2,7 @@ class ProtectedBranch < ActiveRecord::Base include Gitlab::ShellAdapter include ProtectedRef - has_many :merge_access_levels, dependent: :destroy - has_many :push_access_levels, dependent: :destroy - - validates :merge_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." } - validates :push_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." } - - accepts_nested_attributes_for :push_access_levels - accepts_nested_attributes_for :merge_access_levels + protected_ref_access_levels :merge, :push # Check if branch name is marked as protected in the system def self.protected?(project, ref_name) diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb index 83964095516..f38109c0e52 100644 --- a/app/models/protected_tag.rb +++ b/app/models/protected_tag.rb @@ -2,11 +2,7 @@ class ProtectedTag < ActiveRecord::Base include Gitlab::ShellAdapter include ProtectedRef - has_many :create_access_levels, dependent: :destroy - - validates :create_access_levels, length: { is: 1, message: "are restricted to a single instance per protected tag." } - - accepts_nested_attributes_for :create_access_levels + protected_ref_access_levels :create def self.protected?(project, ref_name) self.matching(ref_name, protected_refs: project.protected_tags).present? diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index af9a080f0a2..dd5b346d922 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'js-new-protected-tag' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| .panel.panel-default .panel-heading %h3.panel-title diff --git a/app/views/projects/protected_tags/_dropdown.html.haml b/app/views/projects/protected_tags/_dropdown.html.haml index c8531f96f97..9b6923210f7 100644 --- a/app/views/projects/protected_tags/_dropdown.html.haml +++ b/app/views/projects/protected_tags/_dropdown.html.haml @@ -2,7 +2,7 @@ = dropdown_tag('Select tag or create wildcard', options: { toggle_class: 'js-protected-tag-select js-filter-submit wide git-revision-dropdown-toggle', - filter: true, dropdown_class: "dropdown-menu-selectable capitalize-header git-revision-dropdown", placeholder: "Search protected tag", + filter: true, dropdown_class: "dropdown-menu-selectable capitalize-header git-revision-dropdown", placeholder: "Search protected tags", footer_content: true, data: { show_no: true, show_any: true, show_upcoming: true, selected: params[:protected_tag_name], @@ -10,6 +10,6 @@ %ul.dropdown-footer-list %li - = link_to '#', title: "New Protected Tag", class: "create-new-protected-tag" do + %button{ class: "create-new-protected-tag-button js-create-new-protected-tag", title: "New Protected Tag" } Create wildcard %code diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml index 0bfb1ad191d..663cbd7cd64 100644 --- a/app/views/projects/protected_tags/_index.html.haml +++ b/app/views/projects/protected_tags/_index.html.haml @@ -4,13 +4,14 @@ .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 - Protected tags + Protected Tags %p.prepend-top-20 - By default, Protected tags are designed to: + By default, protected tags are designed to: %ul %li Prevent tag creation by everybody except Masters %li Prevent anyone from updating the tag %li Prevent anyone from deleting the tag + %p.append-bottom-0 Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. .col-lg-9 - if can? current_user, :admin_project, @project = render 'projects/protected_tags/create_protected_tag' diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index 54249ec0db1..f11ce0483a9 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -19,4 +19,4 @@ - if can_admin_project %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_tags/_tags_list.html.haml b/app/views/projects/protected_tags/_tags_list.html.haml index 728afd75b50..d432a5c9113 100644 --- a/app/views/projects/protected_tags/_tags_list.html.haml +++ b/app/views/projects/protected_tags/_tags_list.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default.protected-tags-list.js-protected-tags-list +.panel.panel-default.protected-tags-list - if @protected_tags.empty? .panel-heading %h3.panel-title @@ -13,6 +13,8 @@ %col{ width: "25%" } %col{ width: "25%" } %col{ width: "50%" } + - if can_admin_project + %col %thead %tr %th Protected tag (#{@protected_tags.size}) diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml index 94c3612a449..16fc02fe9f4 100644 --- a/app/views/projects/protected_tags/show.html.haml +++ b/app/views/projects/protected_tags/show.html.haml @@ -5,7 +5,7 @@ %h4.prepend-top-0.ref-name = @protected_ref.name - .col-lg-9 + .col-lg-9.edit_protected_tag %h5 Matching Tags - if @matching_refs.present? .table-responsive diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 2e9646286df..21296a36729 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -144,7 +144,9 @@ merge_access_levels: push_access_levels: - protected_branch create_access_levels: +- user - protected_tag +- group container_repositories: - project - name -- cgit v1.2.1 From 5a31cbcb33d93c8a9ece77b713f0c31cdfd6ee6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Sat, 3 Jun 2017 06:53:10 +0000 Subject: Fix typo on namespace --- doc/install/kubernetes/gitlab_runner_chart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md index 305b4593c73..b8bc0795f2e 100644 --- a/doc/install/kubernetes/gitlab_runner_chart.md +++ b/doc/install/kubernetes/gitlab_runner_chart.md @@ -141,7 +141,7 @@ Once you [have configured](#configuration) GitLab Runner in your `values.yml` fi run the following: ```bash -helm install --namepace --name gitlab-runner -f gitlab/gitlab-runner +helm install --namespace --name gitlab-runner -f gitlab/gitlab-runner ``` - `` is the Kubernetes namespace where you want to install the GitLab Runner. @@ -153,7 +153,7 @@ helm install --namepace --name gitlab-runner -f Once your GitLab Runner Chart is installed, configuration changes and chart updates should we done using `helm upgrade` ```bash -helm upgrade --namepace -f gitlab/gitlab-runner +helm upgrade --namespace -f gitlab/gitlab-runner ``` Where: -- cgit v1.2.1 From a048e4a4717f71ecf1a1780d55639b029be5d71e Mon Sep 17 00:00:00 2001 From: Gustav Ernberg Date: Mon, 29 May 2017 22:18:09 +0200 Subject: Fixed style on unsubscribe page Removed unnecassary logic Added missing 'the' Fix case Fixed specs --- app/views/sent_notifications/unsubscribe.html.haml | 10 ++++------ changelogs/unreleased/issue-23254.yml | 4 ++++ spec/features/unsubscribe_links_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/issue-23254.yml diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml index 9ce6a1aeef5..de52fd00157 100644 --- a/app/views/sent_notifications/unsubscribe.html.haml +++ b/app/views/sent_notifications/unsubscribe.html.haml @@ -1,16 +1,14 @@ - noteable = @sent_notification.noteable -- noteable_type = @sent_notification.noteable_type.humanize(capitalize: false) +- noteable_type = @sent_notification.noteable_type.titleize.downcase - noteable_text = %(#{noteable.title} (#{noteable.to_reference})) - -- page_title "Unsubscribe", noteable_text, @sent_notification.noteable_type.humanize.pluralize, @sent_notification.project.name_with_namespace - +- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.name_with_namespace %h3.page-title - Unsubscribe from #{noteable_type} #{noteable_text} + Unsubscribe from #{noteable_type} %p = succeed '?' do - Are you sure you want to unsubscribe from #{noteable_type} + Are you sure you want to unsubscribe from the #{noteable_type}: = link_to noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable]) %p diff --git a/changelogs/unreleased/issue-23254.yml b/changelogs/unreleased/issue-23254.yml new file mode 100644 index 00000000000..568a7a41c30 --- /dev/null +++ b/changelogs/unreleased/issue-23254.yml @@ -0,0 +1,4 @@ +--- +title: Fixed style on unsubscribe page +merge_request: +author: Gustav Ernberg diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index a23c4ca2b92..8509551ce4a 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -24,8 +24,8 @@ describe 'Unsubscribe links', feature: true do visit body_link expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last) - expect(page).to have_text(%(Unsubscribe from issue #{issue.title} (#{issue.to_reference}))) - expect(page).to have_text(%(Are you sure you want to unsubscribe from issue #{issue.title} (#{issue.to_reference})?)) + expect(page).to have_text(%(Unsubscribe from issue)) + expect(page).to have_text(%(Are you sure you want to unsubscribe from the issue: #{issue.title} (#{issue.to_reference})?)) expect(issue.subscribed?(recipient, project)).to be_truthy click_link 'Unsubscribe' -- cgit v1.2.1 From 44affff387e1a4406f3e030894caf73e8d41a064 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Sat, 3 Jun 2017 21:40:59 -0500 Subject: Use last_activity_at attr when showing the update date in project listing --- app/views/shared/projects/_project.html.haml | 2 +- spec/features/dashboard/projects_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index cf0540afb38..fbc335f6176 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -7,7 +7,7 @@ - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = project_list_cache_key(project) -- updated_tooltip = time_ago_with_tooltip(project.updated_at) +- updated_tooltip = time_ago_with_tooltip(project.last_activity_at) %li.project-row{ class: css_class } = cache(cache_key) do diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index fa3435ab719..3568954a548 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -15,6 +15,15 @@ RSpec.describe 'Dashboard Projects', feature: true do expect(page).to have_content('awesome stuff') end + it 'shows the last_activity_at attribute as the update date' do + now = Time.now + project.update_column(:last_activity_at, now) + + visit dashboard_projects_path + + expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']") + end + context 'when on Starred projects tab' do it 'shows only starred projects' do user.toggle_star(project2) -- cgit v1.2.1 From 46e9161b3286c4a66f4db791945bf7e845911cb1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 3 Jun 2017 23:11:53 -0700 Subject: Fix typo in user activity debug log message --- app/services/users/activity_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb index facf21a7f5c..ab532a1fdcf 100644 --- a/app/services/users/activity_service.rb +++ b/app/services/users/activity_service.rb @@ -16,7 +16,7 @@ module Users def record_activity Gitlab::UserActivities.record(@author.id) - Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username}") + Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username})") end end end -- cgit v1.2.1 From 144f0e0d12c287e034d9f86326c86f2b1bad55ef Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Jun 2017 06:26:34 +0300 Subject: Backport CI codeclimate example doc change from EE Signed-off-by: Dmitriy Zaporozhets --- doc/ci/examples/code_climate.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index bd53f80ce14..c6b9e2065cf 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -1,11 +1,11 @@ # Analyze project code quality with Code Climate CLI -This example shows how to run [Code Climate CLI][cli] on your code by using\ +This example shows how to run [Code Climate CLI][cli] on your code by using GitLab CI and Docker. -First, you need GitLab Runner with [docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor). +First, you need GitLab Runner with [docker-in-docker executor][dind]. -Once you setup the Runner add new job to `.gitlab-ci.yml`: +Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `codeclimate`: ```yaml codeclimate: @@ -25,4 +25,10 @@ codeclimate: This will create a `codeclimate` job in your CI pipeline and will allow you to download and analyze the report artifact in JSON format. +For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically +extracted and shown right in the merge request widget. [Learn more on code quality +diffs in merge requests](../../user/project/merge_requests/code_quality_diff.md). + [cli]: https://github.com/codeclimate/codeclimate +[dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor +[ee]: https://about.gitlab.com/gitlab-ee/ -- cgit v1.2.1 From e341c4d5f528c04f5ef4cb5df10a5ca7b54d8f9e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 5 Jun 2017 03:29:19 +0000 Subject: Update gitlab_chart.md --- doc/install/kubernetes/gitlab_chart.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index b4ffd57afbb..d2442a4fbde 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -207,7 +207,9 @@ its class in an annotation. >**Note:** The Ingress alone doesn't expose GitLab externally. You need to have a Ingress controller setup to do that. Setting up an Ingress controller can be done by installing the `nginx-ingress` helm chart. But be sure -to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md) +to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md). +>**Note:** +If you would like to use the Registry, you will also need to ensure your Ingress supports a [sufficiently large request size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size). #### Preserving Source IPs -- cgit v1.2.1 From 2eec0b73e8201761e9c350621651d2949dea52af Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Jun 2017 09:26:20 +0300 Subject: Fix submodule link to then project under subgroup Before this change only last namespace in full path was extracted. It's fine unless you have a link to submodule under subgroups. In that case self_url? method returns false and link is processed as external. I could not find a proper regex to cover all cases and correctly extract full path to repository and instead used current instance host name to get correct path to namespace and project. Signed-off-by: Dmitriy Zaporozhets --- app/helpers/submodule_helper.rb | 11 +++++++++++ spec/helpers/submodule_helper_spec.rb | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index c0763a8a9c4..8e0a1e2ecdf 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -13,6 +13,17 @@ module SubmoduleHelper if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/ namespace, project = $1, $2 + gitlab_hosts = [Gitlab.config.gitlab.url, + Gitlab.config.gitlab_shell.ssh_path_prefix] + + gitlab_hosts.each do |host| + if url.start_with?(host) + namespace, _, project = url.sub(host, '').rpartition('/') + break + end + end + + namespace.sub!(/\A\//, '') project.rstrip! project.sub!(/\.git\z/, '') diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index b05ae5c2232..cb727430117 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -52,6 +52,14 @@ describe SubmoduleHelper do stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git'].join('')) expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')]) end + + it 'works with subgroups' do + allow(Gitlab.config.gitlab).to receive(:port).and_return(80) # set this just to be sure + allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root') + allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url)) + stub_url(['http://', config.host, '/gitlab/root/gitlab-org/sub/gitlab-ce.git'].join('')) + expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')]) + end end context 'submodule on github.com' do -- cgit v1.2.1 From 9c2ca7aab6a8cdbdb30cdca8a8b58ad843addb9f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Jun 2017 09:37:03 +0300 Subject: Add changelog for submodule subgroup fix Signed-off-by: Dmitriy Zaporozhets --- changelogs/unreleased/dz-fix-submodule-subgroup.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/dz-fix-submodule-subgroup.yml diff --git a/changelogs/unreleased/dz-fix-submodule-subgroup.yml b/changelogs/unreleased/dz-fix-submodule-subgroup.yml new file mode 100644 index 00000000000..20c7c9ce657 --- /dev/null +++ b/changelogs/unreleased/dz-fix-submodule-subgroup.yml @@ -0,0 +1,4 @@ +--- +title: Fix submodule link to then project under subgroup +merge_request: 11906 +author: -- cgit v1.2.1 From 34841c7e547922ce4c6f8be97a0d0736cbaebf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 23 May 2017 20:31:09 +0800 Subject: added Simplified Chinese to I18N --- app/assets/javascripts/locale/zh/app.js | 203 +++++++++++++++++++++++++++++++ lib/gitlab/i18n.rb | 3 +- locale/zh/gitlab.po | 207 ++++++++++++++++++++++++++++++++ locale/zh/gitlab.po.time_stamp | 0 4 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh/app.js create mode 100644 locale/zh/gitlab.po create mode 100644 locale/zh/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js new file mode 100644 index 00000000000..49a0a10b2e1 --- /dev/null +++ b/app/assets/javascripts/locale/zh/app.js @@ -0,0 +1,203 @@ +var locales = locales || {}; +locales['zh'] = { + "domain": "app", + "locale_data": { + "app": { + "": { + "Project-Id-Version": "gitlab 1.0.0", + "Report-Msgid-Bugs-To": "", + "PO-Revision-Date": "2017-05-23 17:36-0800", + "Last-Translator": "htve ", + "Language-Team": "Simplified Chinese", + "Language": "zh", + "MIME-Version": "1.0", + "Content-Type": "text/plain; charset=UTF-8", + "Content-Transfer-Encoding": "8bit", + "Plural-Forms": "nplurals=2; plural=n != 1;", + "lang": "zh", + "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": [ + "最后%d天", + "最后%d天" + ], + "Limited to showing %d event at most": [ + "最多显示%d个事件", + "最多显示%d个事件" + ], + "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": [ + "显示%d个事件", + "显示%d个事件" + ], + "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.": [ + "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + ], + "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.": [ + "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + ], + "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.": [ + "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 5ab3eeb3aff..f1a284b1581 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -5,7 +5,8 @@ module Gitlab AVAILABLE_LANGUAGES = { 'en' => 'English', 'es' => 'Español', - 'de' => 'Deutsch' + 'de' => 'Deutsch', + 'zh' => '简体中文' }.freeze def available_locales diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po new file mode 100644 index 00000000000..3e8a8190ff5 --- /dev/null +++ b/locale/zh/gitlab.po @@ -0,0 +1,207 @@ +# Simplified Chinese translations for gitlab package. +# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# htve , 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2017-05-23 15:35-0800\n" +"Last-Translator: htve \n" +"Language-Team: Simplified Chinese\n" +"Language: zh\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] "最后%d天" +msgstr[1] "最后%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多显示%d个事件" +msgstr[1] "最多显示%d个事件" + +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] "显示%d个事件" +msgstr[1] "显示%d个事件" + +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 "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + +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 "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + +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 "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5 + 7)/ 2 = 6。" + +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/zh/gitlab.po.time_stamp b/locale/zh/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 61df40cab9477c3f6172b08651bc303edf4cc84a Mon Sep 17 00:00:00 2001 From: htve Date: Tue, 23 May 2017 12:42:37 +0000 Subject: Update i18n.rb style --- lib/gitlab/i18n.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index f1a284b1581..e9acb1828aa 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,7 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh' => '简体中文' + 'zh' => '简体中文' }.freeze def available_locales -- cgit v1.2.1 From 3f7134a4091015c8b27e52d0115f9af775905344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 26 May 2017 13:15:32 +0800 Subject: optimize part of the translation --- app/assets/javascripts/locale/zh/app.js | 56 ++++++++++++++++---------------- locale/zh/gitlab.po | 57 +++++++++++++++++---------------- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js index 49a0a10b2e1..e266b91cc5b 100644 --- a/app/assets/javascripts/locale/zh/app.js +++ b/app/assets/javascripts/locale/zh/app.js @@ -6,7 +6,7 @@ locales['zh'] = { "": { "Project-Id-Version": "gitlab 1.0.0", "Report-Msgid-Bugs-To": "", - "PO-Revision-Date": "2017-05-23 17:36-0800", + "PO-Revision-Date": "2017-05-26 13:08+0800", "Last-Translator": "htve ", "Language-Team": "Simplified Chinese", "Language": "zh", @@ -26,13 +26,13 @@ locales['zh'] = { "提交" ], "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": [ "计划" @@ -44,7 +44,7 @@ locales['zh'] = { "评审" ], "CycleAnalyticsStage|Staging": [ - "排期" + "预发布" ], "CycleAnalyticsStage|Test": [ "测试" @@ -57,16 +57,16 @@ locales['zh'] = { "首个推送" ], "FirstPushedBy|pushed by": [ - "作者:" + "推送者:" ], "From issue creation until deploy to production": [ - "从问题创建到部署到生产" + "从创建议题到部署到生产环境" ], "From merge request merge until deploy to production": [ "从合并请求的合并到部署至生产环境" ], "Introducing Cycle Analytics": [ - "引入周期分析" + "周期分析简介" ], "Last %d day": [ "最后%d天", @@ -80,7 +80,7 @@ locales['zh'] = { "平均数" ], "New Issue": [ - "新问题", + "新议题", "新问题" ], "Not available": [ @@ -93,13 +93,13 @@ locales['zh'] = { "开始于" ], "Pipeline Health": [ - "流水线健康" + "流水线健康指标" ], "ProjectLifecycle|Stage": [ "项目生命周期" ], "Read more": [ - "阅读更多" + "了解更多" ], "Related Commits": [ "相关的提交" @@ -108,7 +108,7 @@ locales['zh'] = { "相关的部署作业" ], "Related Issues": [ - "相关的问题" + "相关的议题" ], "Related Jobs": [ "相关的作业" @@ -124,49 +124,49 @@ locales['zh'] = { "显示%d个事件" ], "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.": [ - "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" + "预发布阶段概述了合并请求的合并到部署代码到生产环境的时间。首次部署到生产环境后,数据将自动添加到此处。" ], "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.": [ - "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" + "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" ], "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.": [ - "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5+7)/2 = 6。" + "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(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": [ "小时", @@ -186,7 +186,7 @@ locales['zh'] = { "所有提交和合并的总测试时间" ], "Want to see the data? Please ask an administrator for access.": [ - "想查看数据?请向管理员查询。" + "权限不足。如需查看相关数据,请向管理员申请权限。" ], "We don't have enough data to show this stage.": [ "我们没有足够的数据显示这个阶段。" diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po index 3e8a8190ff5..be61691625a 100644 --- a/locale/zh/gitlab.po +++ b/locale/zh/gitlab.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-23 15:35-0800\n" +"PO-Revision-Date: 2017-05-26 13:08+0800\n" "Last-Translator: htve \n" "Language-Team: Simplified Chinese\n" "Language: zh\n" @@ -26,13 +26,14 @@ 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 "周期分析概述了您的项目中从创意到生产所需的时间。" +msgstr "周期分析概述了项目从想法到产品实现的各阶段所需的时间。" msgid "CycleAnalyticsStage|Code" -msgstr "代码" +msgstr "编码" +# '议题' sounds good, but most of the software on the market will 'issue' translated into '问题' msgid "CycleAnalyticsStage|Issue" -msgstr "问题" +msgstr "议题" msgid "CycleAnalyticsStage|Plan" msgstr "计划" @@ -44,7 +45,7 @@ msgid "CycleAnalyticsStage|Review" msgstr "评审" msgid "CycleAnalyticsStage|Staging" -msgstr "排期" +msgstr "预发布" msgid "CycleAnalyticsStage|Test" msgstr "测试" @@ -58,16 +59,16 @@ msgid "FirstPushedBy|First" msgstr "首个推送" msgid "FirstPushedBy|pushed by" -msgstr "作者:" +msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "从问题创建到部署到生产" +msgstr "从创建议题到部署到生产环境" msgid "From merge request merge until deploy to production" msgstr "从合并请求的合并到部署至生产环境" msgid "Introducing Cycle Analytics" -msgstr "引入周期分析" +msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" @@ -84,7 +85,7 @@ msgstr "平均数" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "新问题" +msgstr[0] "新议题" msgstr[1] "新问题" msgid "Not available" @@ -97,13 +98,13 @@ msgid "OpenedNDaysAgo|Opened" msgstr "开始于" msgid "Pipeline Health" -msgstr "流水线健康" +msgstr "流水线健康指标" msgid "ProjectLifecycle|Stage" msgstr "项目生命周期" msgid "Read more" -msgstr "阅读更多" +msgstr "了解更多" msgid "Related Commits" msgstr "相关的提交" @@ -112,7 +113,7 @@ msgid "Related Deployed Jobs" msgstr "相关的部署作业" msgid "Related Issues" -msgstr "相关的问题" +msgstr "相关的议题" msgid "Related Jobs" msgstr "相关的作业" @@ -129,49 +130,49 @@ msgstr[0] "显示%d个事件" msgstr[1] "显示%d个事件" 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 "编码阶段显示从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" +msgstr "编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" msgid "The collection of events added to the data gathered for that stage." -msgstr "将收集的事件添加到为该阶段收集的数据中。" +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 "问题阶段显示了从创建问题到将问题分配给里程碑所需的时间,或将问题添加到问题委员会的列表中。开始创建问题以查看此阶段的数据" +msgstr "议题阶段概述了从创建议题到将议题分配给里程碑或将议题添加到议题看板列表中所需的时间。开始创建议题以查看此阶段的数据。" msgid "The phase of the development lifecycle." -msgstr "开发生命周期中的各个阶段。" +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 "规划阶段显示了从上一步到推送第一次提交的时间。一旦你推出第一次提交,这个时候就会自动添加。" +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 "生产阶段显示创建问题和将代码部署到生产之间的总时间。一旦完成完整的想法到生产周期,数据就会自动添加。" +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 "审查阶段显示从合并请求到合并的时间。合并您的第一个合并请求后,数据将自动添加。" +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 "分期阶段显示合并MR和部署代码到生产环境之间的时间。首次部署到生产时,数据将自动添加。" +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 "测试阶段显示GitLab CI为相关合并请求运行每个流水线所需的时间。数据将在您的第一个管道完成运行后自动添加。" +msgstr "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由该阶段收集的每个数据条目所用的时间。" +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 "该值位于一系列观察值的平均值。例如,在3,5,9之间,平均值是5。在3,5,7,8之间,平均值是(5 + 7)/ 2 = 6。" +msgstr "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "发布问题之前的时间" +msgstr "创建议题之前的时间" msgid "Time before an issue starts implementation" -msgstr "问题开始实施之前的时间" +msgstr "从创建议题到开始编码的时间" msgid "Time between merge request creation and merge/close" -msgstr "合并请求创建到合并或关闭之间的时间" +msgstr "从创建合并请求到合并或关闭合并请求的时间" msgid "Time until first merge request" -msgstr "时间到第一个合并请求" +msgstr "创建第一个合并请求之前的时间" msgid "Time|hr" msgid_plural "Time|hrs" @@ -193,7 +194,7 @@ msgid "Total test time for all commits/merges" msgstr "所有提交和合并的总测试时间" msgid "Want to see the data? Please ask an administrator for access." -msgstr "想查看数据?请向管理员查询。" +msgstr "权限不足。如需查看相关数据,请向管理员申请权限。" msgid "We don't have enough data to show this stage." msgstr "我们没有足够的数据显示这个阶段。" -- cgit v1.2.1 From edcecf27ea9d0f4d053d5aafe9071bd5f3531343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 26 May 2017 16:42:48 +0800 Subject: fix plural form of translation --- app/assets/javascripts/locale/zh/app.js | 24 ++++------- locale/zh/gitlab.po | 75 ++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js index e266b91cc5b..18b834ef32e 100644 --- a/app/assets/javascripts/locale/zh/app.js +++ b/app/assets/javascripts/locale/zh/app.js @@ -6,23 +6,23 @@ locales['zh'] = { "": { "Project-Id-Version": "gitlab 1.0.0", "Report-Msgid-Bugs-To": "", - "PO-Revision-Date": "2017-05-26 13:08+0800", - "Last-Translator": "htve ", - "Language-Team": "Simplified Chinese", - "Language": "zh", + "POT-Creation-Date": "2017-05-04 19:24-0500", + "PO-Revision-Date": "2017-05-04 19:24-0500", + "Last-Translator": "HuangTao , 2017", + "Language-Team": "Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", - "Plural-Forms": "nplurals=2; plural=n != 1;", + "Language": "zh_CN", + "Plural-Forms": "nplurals=1; plural=0;", "lang": "zh", "domain": "app", - "plural_forms": "nplurals=2; plural=n != 1;" + "plural_forms": "nplurals=1; plural=0;" }, "ByAuthor|by": [ "作者:" ], "Commit": [ - "提交", "提交" ], "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.": [ @@ -50,7 +50,6 @@ locales['zh'] = { "测试" ], "Deploy": [ - "部署", "部署" ], "FirstPushedBy|First": [ @@ -69,19 +68,16 @@ locales['zh'] = { "周期分析简介" ], "Last %d day": [ - "最后%d天", "最后%d天" ], "Limited to showing %d event at most": [ - "最多显示%d个事件", "最多显示%d个事件" ], "Median": [ "平均数" ], "New Issue": [ - "新议题", - "新问题" + "新议题" ], "Not available": [ "不可用" @@ -120,7 +116,6 @@ locales['zh'] = { "相关已合并的合并请求" ], "Showing %d event": [ - "显示%d个事件", "显示%d个事件" ], "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.": [ @@ -169,11 +164,9 @@ locales['zh'] = { "创建第一个合并请求之前的时间" ], "Time|hr": [ - "小时", "小时" ], "Time|min": [ - "分钟", "分钟" ], "Time|s": [ @@ -195,7 +188,6 @@ locales['zh'] = { "您需要相关的权限。" ], "day": [ - "天", "天" ] } diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po index be61691625a..72c9fe71d15 100644 --- a/locale/zh/gitlab.po +++ b/locale/zh/gitlab.po @@ -1,21 +1,22 @@ -# Simplified Chinese translations for gitlab package. -# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gitlab package. -# htve , 2017. -# +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-26 13:08+0800\n" -"Last-Translator: htve \n" -"Language-Team: Simplified Chinese\n" -"Language: zh\n" +"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"PO-Revision-Date: 2017-05-04 19:24-0500\n" +"Last-Translator: HuangTao , 2017\n" +"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\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" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" msgid "ByAuthor|by" msgstr "作者:" @@ -23,15 +24,15 @@ 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." +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 "编码" -# '议题' sounds good, but most of the software on the market will 'issue' translated into '问题' msgid "CycleAnalyticsStage|Issue" msgstr "议题" @@ -53,7 +54,6 @@ msgstr "测试" msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" -msgstr[1] "部署" msgid "FirstPushedBy|First" msgstr "首个推送" @@ -73,12 +73,10 @@ msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最后%d天" -msgstr[1] "最后%d天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "最多显示%d个事件" -msgstr[1] "最多显示%d个事件" msgid "Median" msgstr "平均数" @@ -86,7 +84,6 @@ msgstr "平均数" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新议题" -msgstr[1] "新问题" msgid "Not available" msgstr "不可用" @@ -127,39 +124,62 @@ msgstr "相关已合并的合并请求" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "显示%d个事件" -msgstr[1] "显示%d个事件" -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." +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." +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." +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." +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." +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." +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." +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" 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." +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 "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" @@ -177,12 +197,10 @@ 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 "秒" @@ -205,4 +223,3 @@ msgstr "您需要相关的权限。" msgid "day" msgid_plural "days" msgstr[0] "天" -msgstr[1] "天" -- cgit v1.2.1 From c267a86c234ba7de271244011509bd72c4f789c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:20:37 +0800 Subject: Change the language name zh to zh_CN --- app/assets/javascripts/locale/zh/app.js | 195 ------------------------- app/assets/javascripts/locale/zh_CN/app.js | 1 + lib/gitlab/i18n.rb | 2 +- locale/zh/gitlab.po | 225 ----------------------------- locale/zh/gitlab.po.time_stamp | 0 locale/zh_CN/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_CN/gitlab.po.time_stamp | 0 7 files changed, 227 insertions(+), 421 deletions(-) delete mode 100644 app/assets/javascripts/locale/zh/app.js create mode 100644 app/assets/javascripts/locale/zh_CN/app.js delete mode 100644 locale/zh/gitlab.po delete mode 100644 locale/zh/gitlab.po.time_stamp create mode 100644 locale/zh_CN/gitlab.po create mode 100644 locale/zh_CN/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh/app.js b/app/assets/javascripts/locale/zh/app.js deleted file mode 100644 index 18b834ef32e..00000000000 --- a/app/assets/javascripts/locale/zh/app.js +++ /dev/null @@ -1,195 +0,0 @@ -var locales = locales || {}; -locales['zh'] = { - "domain": "app", - "locale_data": { - "app": { - "": { - "Project-Id-Version": "gitlab 1.0.0", - "Report-Msgid-Bugs-To": "", - "POT-Creation-Date": "2017-05-04 19:24-0500", - "PO-Revision-Date": "2017-05-04 19:24-0500", - "Last-Translator": "HuangTao , 2017", - "Language-Team": "Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)", - "MIME-Version": "1.0", - "Content-Type": "text/plain; charset=UTF-8", - "Content-Transfer-Encoding": "8bit", - "Language": "zh_CN", - "Plural-Forms": "nplurals=1; plural=0;", - "lang": "zh", - "domain": "app", - "plural_forms": "nplurals=1; plural=0;" - }, - "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": [ - "最后%d天" - ], - "Limited to showing %d event at most": [ - "最多显示%d个事件" - ], - "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": [ - "显示%d个事件" - ], - "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.": [ - "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" - ], - "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.": [ - "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(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/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js new file mode 100644 index 00000000000..913235ead5d --- /dev/null +++ b/app/assets/javascripts/locale/zh_CN/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后%d天"],"Limited to showing %d event at most":["最多显示%d个事件"],"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":["显示%d个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。"],"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.":["该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index e9acb1828aa..5e61968d892 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,7 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh' => '简体中文' + 'zh_CN' => '简体中文' }.freeze def available_locales diff --git a/locale/zh/gitlab.po b/locale/zh/gitlab.po deleted file mode 100644 index 72c9fe71d15..00000000000 --- a/locale/zh/gitlab.po +++ /dev/null @@ -1,225 +0,0 @@ -# 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 , 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: HuangTao , 2017\n" -"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -msgid "ByAuthor|by" -msgstr "作者:" - -msgid "Commit" -msgid_plural "Commits" -msgstr[0] "提交" - -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] "部署" - -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] "最后%d天" - -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多显示%d个事件" - -msgid "Median" -msgstr "平均数" - -msgid "New Issue" -msgid_plural "New Issues" -msgstr[0] "新议题" - -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] "显示%d个事件" - -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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" - -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 "该值概述了一系列观察值的平均值。例如,在3、5、9之间,平均值是5。在3、5、7、8之间,平均值是(5 + 7)/ 2 = 6。" - -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] "小时" - -msgid "Time|min" -msgid_plural "Time|mins" -msgstr[0] "分钟" - -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] "天" diff --git a/locale/zh/gitlab.po.time_stamp b/locale/zh/gitlab.po.time_stamp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po new file mode 100644 index 00000000000..5fb8dbe5fdd --- /dev/null +++ b/locale/zh_CN/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最后%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多显示%d个事件" + +msgid "Median" +msgstr "中位数" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新议题" + +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] "显示%d个事件" + +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" + +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 "该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(5 + 7)/ 2 = 6。" + +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] "小时" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分钟" + +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] "天" diff --git a/locale/zh_CN/gitlab.po.time_stamp b/locale/zh_CN/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 0f4fa4c7708d39c618e32967f2dc1b8d73ebc946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:22:01 +0800 Subject: Add translation zh_HK to I18N --- app/assets/javascripts/locale/zh_HK/app.js | 1 + lib/gitlab/i18n.rb | 3 +- locale/zh_HK/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_HK/gitlab.po.time_stamp | 0 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh_HK/app.js create mode 100644 locale/zh_HK/gitlab.po create mode 100644 locale/zh_HK/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js new file mode 100644 index 00000000000..721dd8efadf --- /dev/null +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 5e61968d892..093a9653988 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,7 +6,8 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh_CN' => '简体中文' + 'zh_CN' => '简体中文', + 'zh_HK' => '繁體中文(香港)' }.freeze def available_locales diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po new file mode 100644 index 00000000000..95c3fe58da8 --- /dev/null +++ b/locale/zh_HK/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_HK\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最後%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多顯示%d個事件" + +msgid "Median" +msgstr "中位數" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新議題" + +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] "顯示%d個事件" + +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" + +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" + +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] "小時" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分鐘" + +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] "天" diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From fc6999687095d34609af268cfe34009cd40bb8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Sat, 27 May 2017 10:23:26 +0800 Subject: Add translation zh_TW to I18N --- app/assets/javascripts/locale/zh_TW/app.js | 1 + lib/gitlab/i18n.rb | 3 +- locale/zh_TW/gitlab.po | 225 +++++++++++++++++++++++++++++ locale/zh_TW/gitlab.po.time_stamp | 0 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/locale/zh_TW/app.js create mode 100644 locale/zh_TW/gitlab.po create mode 100644 locale/zh_TW/gitlab.po.time_stamp diff --git a/app/assets/javascripts/locale/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js new file mode 100644 index 00000000000..09fdd332193 --- /dev/null +++ b/app/assets/javascripts/locale/zh_TW/app.js @@ -0,0 +1 @@ +var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 093a9653988..8bedf477b07 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -7,7 +7,8 @@ module Gitlab 'es' => 'Español', 'de' => 'Deutsch', 'zh_CN' => '简体中文', - 'zh_HK' => '繁體中文(香港)' + 'zh_HK' => '繁體中文(香港)', + 'zh_TW' => '繁體中文(臺灣)' }.freeze def available_locales diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po new file mode 100644 index 00000000000..2cb9da2b5cd --- /dev/null +++ b/locale/zh_TW/gitlab.po @@ -0,0 +1,225 @@ +# 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 , 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: HuangTao , 2017\n" +"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "ByAuthor|by" +msgstr "作者:" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "提交" + +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] "部署" + +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] "最後%d天" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "最多顯示%d個事件" + +msgid "Median" +msgstr "中位數" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新議題" + +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] "顯示%d個事件" + +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" + +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" + +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] "小時" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分鐘" + +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] "天" diff --git a/locale/zh_TW/gitlab.po.time_stamp b/locale/zh_TW/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 6b9ea6dbd230cd4bc6d0f866f36732bc6059a979 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Sat, 27 May 2017 03:34:55 +0000 Subject: Update i18n.rb Indent style --- lib/gitlab/i18n.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 8bedf477b07..f7ac48f7dbd 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -6,9 +6,9 @@ module Gitlab 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch', - 'zh_CN' => '简体中文', - 'zh_HK' => '繁體中文(香港)', - 'zh_TW' => '繁體中文(臺灣)' + 'zh_CN' => '简体中文', + 'zh_HK' => '繁體中文(香港)', + 'zh_TW' => '繁體中文(臺灣)' }.freeze def available_locales -- cgit v1.2.1 From a8b66b4a0307994dd56bb879249dbd08b33395f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 31 May 2017 17:21:17 +0800 Subject: Optimize translation content 1. Optimize the translation of zh-TW 2. Synchronous zh-CN, zh-HK translation --- app/assets/javascripts/locale/zh_CN/app.js | 2 +- app/assets/javascripts/locale/zh_HK/app.js | 2 +- app/assets/javascripts/locale/zh_TW/app.js | 2 +- locale/zh_CN/gitlab.po | 40 ++++++++-------- locale/zh_HK/gitlab.po | 54 +++++++++++----------- locale/zh_TW/gitlab.po | 74 +++++++++++++++--------------- 6 files changed, 87 insertions(+), 87 deletions(-) diff --git a/app/assets/javascripts/locale/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js index 913235ead5d..9525bc88190 100644 --- a/app/assets/javascripts/locale/zh_CN/app.js +++ b/app/assets/javascripts/locale/zh_CN/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后%d天"],"Limited to showing %d event at most":["最多显示%d个事件"],"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":["显示%d个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。"],"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.":["该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(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 +var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (China) (https://www.transifex.com/gitlab-zh/teams/75177/zh_CN/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_CN","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最后 %d 天"],"Limited to showing %d event at most":["最多显示 %d 个事件"],"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":["显示 %d 个事件"],"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.":["测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"],"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.":["中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (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/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 721dd8efadf..fd0bcd988c5 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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 +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後 %d 天"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"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":["顯示 %d 個事件"],"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.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"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.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (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/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js index 09fdd332193..79904d17bf6 100644 --- a/app/assets/javascripts/locale/zh_TW/app.js +++ b/app/assets/javascripts/locale/zh_TW/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後%d天"],"Limited to showing %d event at most":["最多顯示%d個事件"],"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":["顯示%d個事件"],"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.":["測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。"],"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.":["該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(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 +var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"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":["最後 %d 天"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"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":["顯示 %d 個事件"],"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.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (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/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 5fb8dbe5fdd..c2d69b122e2 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -56,27 +56,27 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首个推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "从创建议题到部署到生产环境" +msgstr "从创建议题到部署至生产环境" msgid "From merge request merge until deploy to production" -msgstr "从合并请求的合并到部署至生产环境" +msgstr "从合并请求被合并后到部署至生产环境" msgid "Introducing Cycle Analytics" msgstr "周期分析简介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最后%d天" +msgstr[0] "最后 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多显示%d个事件" +msgstr[0] "最多显示 %d 个事件" msgid "Median" msgstr "中位数" @@ -86,7 +86,7 @@ msgid_plural "New Issues" msgstr[0] "新议题" msgid "Not available" -msgstr "不可用" +msgstr "数据不足" msgid "Not enough data" msgstr "数据不足" @@ -123,7 +123,7 @@ msgstr "相关已合并的合并请求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "显示%d个事件" +msgstr[0] "显示 %d 个事件" msgid "" "The coding stage shows the time from the first commit to creating the merge " @@ -132,13 +132,13 @@ msgid "" msgstr "编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。" msgid "The collection of events added to the data gathered for that stage." -msgstr "将收集的事件添加到该阶段的相关统计数据中。" +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 "议题阶段概述了从创建议题到将议题分配给里程碑或将议题添加到议题看板列表中所需的时间。开始创建议题以查看此阶段的数据。" +msgstr "议题阶段概述了从创建议题到将议题设置里程碑或将议题添加到议题看板的时间。开始创建议题以查看此阶段的数据。" msgid "The phase of the development lifecycle." msgstr "项目生命周期中的各个阶段。" @@ -147,49 +147,49 @@ 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 "计划阶段概述了从议题到推送第一次提交的时间。当你第一次推送提交,数据将自动添加到此处。" +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 "生产阶段概述了从创建议题到将代码部署到生产环境的时间。一旦完成完整的想法到部署生产,数据将自动添加到此处。" +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 "评审阶段概述了从创建合并请求到合并的时间。在您合并第一个合并请求后,数据将自动添加到此处。" +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 "预发布阶段概述了合并请求的合并到部署代码到生产环境的时间。首次部署到生产环境后,数据将自动添加到此处。" +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 "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。在您的第一个流水线完成运行后,数据将自动添加到此处。" +msgstr "测试阶段概述了GitLab CI为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由该阶段收集的每个数据条目所用的时间总和。" +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 "该值概述了一系列观察值的中位数。例如,在3、5、9之间,中位数是5。在3、5、7、8之间,中位数是(5 + 7)/ 2 = 6。" +msgstr "中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "创建议题之前的时间" +msgstr "议题被列入日程表的时间" msgid "Time before an issue starts implementation" -msgstr "从创建议题到开始编码的时间" +msgstr "开始进行编码前的时间" msgid "Time between merge request creation and merge/close" -msgstr "从创建合并请求到合并或关闭合并请求的时间" +msgstr "从创建合并请求到被合并或关闭的时间" msgid "Time until first merge request" msgstr "创建第一个合并请求之前的时间" @@ -215,7 +215,7 @@ 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 "我们没有足够的数据显示这个阶段。" +msgstr "该阶段的数据不足,无法显示。" msgid "You need permission." msgstr "您需要相关的权限。" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 95c3fe58da8..6d56b2897fa 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -28,7 +28,7 @@ msgstr[0] "提交" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." -msgstr "周期分析概述了項目從想法到產品實現的各階段所需的時間。" +msgstr "週期分析概述了項目從想法到產品實現的各階段所需的時間。" msgid "CycleAnalyticsStage|Code" msgstr "編碼" @@ -56,7 +56,7 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首個推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" @@ -65,18 +65,18 @@ msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" -msgstr "從合並請求的合並到部署至生產環境" +msgstr "從合併請求的合併到部署至生產環境" msgid "Introducing Cycle Analytics" -msgstr "周期分析簡介" +msgstr "週期分析簡介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後%d天" +msgstr[0] "最後 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示%d個事件" +msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" @@ -98,7 +98,7 @@ msgid "Pipeline Health" msgstr "流水線健康指標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命周期" +msgstr "項目生命週期" msgid "Read more" msgstr "了解更多" @@ -116,83 +116,83 @@ msgid "Related Jobs" msgstr "相關的作業" msgid "Related Merge Requests" -msgstr "相關的合並請求" +msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合並的合並請求" +msgstr "相關已合併的合並請求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "顯示%d個事件" +msgstr[0] "顯示 %d 個事件" 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 "編碼階段概述了從第壹次提交到創建合並請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." -msgstr "將收集的事件添加到該階段的相關統計數據中。" +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 "議題階段概述了從創建議題到將議題分配給裏程碑或將議題添加到議題看板列表中所需的時間。開始創建議題以查看此階段的數據。" +msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" msgid "The phase of the development lifecycle." -msgstr "項目生命周期中的各個階段。" +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 "計劃階段概述了從議題到推送第壹次提交的時間。當妳第壹次推送提交,數據將自動添加到此處。" +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 "生產階段概述了從創建議題到將代碼部署到生產環境的時間。壹旦完成完整的想法到部署生產,數據將自動添加到此處。" +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 "評審階段概述了從創建合並請求到合並的時間。在您合並第壹個合並請求後,數據將自動添加到此處。" +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 "預發布階段概述了合並請求的合並到部署代碼到生產環境的時間。首次部署到生產環境後,數據將自動添加到此處。" +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" +msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由該階段收集的每個數據條目所用的時間總和。" +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" +msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "創建議題之前的時間" +msgstr "議題被列入日程表的時間" msgid "Time before an issue starts implementation" -msgstr "從創建議題到開始編碼的時間" +msgstr "開始進行編碼前的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合並請求到合並或關閉合並請求的時間" +msgstr "從創建合併請求到被合並或關閉的時間" msgid "Time until first merge request" -msgstr "創建第壹個合並請求之前的時間" +msgstr "創建第壹個合併請求之前的時間" msgid "Time|hr" msgid_plural "Time|hrs" @@ -209,13 +209,13 @@ msgid "Total Time" msgstr "總時間" msgid "Total test time for all commits/merges" -msgstr "所有提交和合並的總測試時間" +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 "我們沒有足夠的數據顯示這個階段。" +msgstr "該階段的數據不足,無法顯示。" msgid "You need permission." msgstr "您需要相關的權限。" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 2cb9da2b5cd..0caf35a915b 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -23,15 +23,15 @@ msgstr "作者:" msgid "Commit" msgid_plural "Commits" -msgstr[0] "提交" +msgstr[0] "送交" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." -msgstr "周期分析概述了項目從想法到產品實現的各階段所需的時間。" +msgstr "週期分析概述了你的專案從想法到產品實現,各階段所需的時間。" msgid "CycleAnalyticsStage|Code" -msgstr "編碼" +msgstr "程式開發" msgid "CycleAnalyticsStage|Issue" msgstr "議題" @@ -40,13 +40,13 @@ msgid "CycleAnalyticsStage|Plan" msgstr "計劃" msgid "CycleAnalyticsStage|Production" -msgstr "生產" +msgstr "上線" msgid "CycleAnalyticsStage|Review" -msgstr "評審" +msgstr "複閱" msgid "CycleAnalyticsStage|Staging" -msgstr "預發布" +msgstr "預備" msgid "CycleAnalyticsStage|Test" msgstr "測試" @@ -56,27 +56,27 @@ msgid_plural "Deploys" msgstr[0] "部署" msgid "FirstPushedBy|First" -msgstr "首個推送" +msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" msgid "From issue creation until deploy to production" -msgstr "從創建議題到部署到生產環境" +msgstr "從議題建立至線上部署" msgid "From merge request merge until deploy to production" -msgstr "從合並請求的合並到部署至生產環境" +msgstr "從請求被合併後至線上部署" msgid "Introducing Cycle Analytics" -msgstr "周期分析簡介" +msgstr "週期分析簡介" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後%d天" +msgstr[0] "最後 %d 天" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "最多顯示%d個事件" +msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" @@ -86,10 +86,10 @@ msgid_plural "New Issues" msgstr[0] "新議題" msgid "Not available" -msgstr "不可用" +msgstr "無法使用" msgid "Not enough data" -msgstr "數據不足" +msgstr "資料不足" msgid "OpenedNDaysAgo|Opened" msgstr "開始於" @@ -98,13 +98,13 @@ msgid "Pipeline Health" msgstr "流水線健康指標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命周期" +msgstr "專案生命週期" msgid "Read more" msgstr "了解更多" msgid "Related Commits" -msgstr "相關的提交" +msgstr "相關的送交" msgid "Related Deployed Jobs" msgstr "相關的部署作業" @@ -116,83 +116,83 @@ msgid "Related Jobs" msgstr "相關的作業" msgid "Related Merge Requests" -msgstr "相關的合並請求" +msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合並的合並請求" +msgstr "相關已合併的請求" msgid "Showing %d event" msgid_plural "Showing %d events" -msgstr[0] "顯示%d個事件" +msgstr[0] "顯示 %d 個事件" 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 "編碼階段概述了從第壹次提交到創建合並請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。" msgid "The collection of events added to the data gathered for that stage." -msgstr "將收集的事件添加到該階段的相關統計數據中。" +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 "議題階段概述了從創建議題到將議題分配給裏程碑或將議題添加到議題看板列表中所需的時間。開始創建議題以查看此階段的數據。" +msgstr "議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。" msgid "The phase of the development lifecycle." -msgstr "項目生命周期中的各個階段。" +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 "計劃階段概述了從議題到推送第壹次提交的時間。當妳第壹次推送提交,數據將自動添加到此處。" +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 "生產階段概述了從創建議題到將代碼部署到生產環境的時間。壹旦完成完整的想法到部署生產,數據將自動添加到此處。" +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 "評審階段概述了從創建合並請求到合並的時間。在您合並第壹個合並請求後,數據將自動添加到此處。" +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 "預發布階段概述了合並請求的合並到部署代碼到生產環境的時間。首次部署到生產環境後,數據將自動添加到此處。" +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 "測試階段概述了GitLab CI為相關合並請求運行每個流水線所需的時間。在您的第壹個流水線完成運行後,數據將自動添加到此處。" +msgstr "測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。" msgid "The time taken by each data entry gathered by that stage." -msgstr "由該階段收集的每個數據條目所用的時間總和。" +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 "該值概述了壹系列觀察值的中位數。例如,在3、5、9之間,中位數是5。在3、5、7、8之間,中位數是(5 + 7)/ 2 = 6。" +msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" -msgstr "創建議題之前的時間" +msgstr "議題被列入日程表的時間" msgid "Time before an issue starts implementation" -msgstr "從創建議題到開始編碼的時間" +msgstr "議題等待開始實作的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合並請求到合並或關閉合並請求的時間" +msgstr "合併請求被合併或是關閉的時間" msgid "Time until first merge request" -msgstr "創建第壹個合並請求之前的時間" +msgstr "第一個合併請求被建立前的時間" msgid "Time|hr" msgid_plural "Time|hrs" @@ -209,13 +209,13 @@ msgid "Total Time" msgstr "總時間" msgid "Total test time for all commits/merges" -msgstr "所有提交和合並的總測試時間" +msgstr "所有送交和合併的總測試時間" msgid "Want to see the data? Please ask an administrator for access." -msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" +msgstr "權限不足。如需查看相關資料,請向管理員申請權限。" msgid "We don't have enough data to show this stage." -msgstr "我們沒有足夠的數據顯示這個階段。" +msgstr "因該階段的資料不足而無法顯示相關資訊" msgid "You need permission." msgstr "您需要相關的權限。" -- cgit v1.2.1 From 91bf43873d7ae0c139cc8442597197fef3e0c109 Mon Sep 17 00:00:00 2001 From: Andrew Featherstone Date: Mon, 5 Jun 2017 09:48:26 +0000 Subject: Corrected spelling mistake in permissions.md "it total" -> "in total". --- doc/user/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index b0145b0a759..3fda47b9e34 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -126,7 +126,7 @@ which visibility level you select on project settings. ## GitLab CI GitLab CI permissions rely on the role the user has in GitLab. There are four -permission levels it total: +permission levels in total: - admin - master -- cgit v1.2.1 From 5db229fb45c98424425bf14c6b9e4ede8ccef1d1 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Jun 2017 15:13:10 +0100 Subject: Allow group reporters to manage group labels Previously, only group masters could do this. However, project reporters can manage project labels, so there doesn't seem to be any need to restrict group labels further. Also, save a query or two by getting a single GroupMember object to find out if the user is a master or not. --- app/models/group.rb | 10 +++++++ app/models/member.rb | 4 +++ app/models/members/group_member.rb | 4 --- app/models/members/project_member.rb | 4 --- app/policies/group_policy.rb | 17 +++++++----- ...issions-for-project-labels-and-group-labels.yml | 4 +++ spec/policies/group_policy_spec.rb | 32 ++++++++++++++++++++-- 7 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml diff --git a/app/models/group.rb b/app/models/group.rb index be944da5a67..5bb2cdc5eff 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -222,6 +222,16 @@ class Group < Namespace User.where(id: members_with_parents.select(:user_id)) end + def max_member_access_for_user(user) + return GroupMember::OWNER if user.admin? + + members_with_parents. + where(user_id: user). + reorder(access_level: :desc). + first&. + access_level || GroupMember::NO_ACCESS + end + def mattermost_team_params max_length = 59 diff --git a/app/models/member.rb b/app/models/member.rb index 7228e82e978..29f9d61e870 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -200,6 +200,10 @@ class Member < ActiveRecord::Base source_type end + def access_field + access_level + end + def invite? self.invite_token.present? end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 28e10bc6172..47040f95533 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -25,10 +25,6 @@ class GroupMember < Member source end - def access_field - access_level - end - # Because source_type is `Namespace`... def real_source_type 'Group' diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index b3a91feb091..c0e17f4bfc8 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -79,10 +79,6 @@ class ProjectMember < Member end end - def access_field - access_level - end - def project source end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 87398303c68..fb07298c6c2 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -4,22 +4,25 @@ class GroupPolicy < BasePolicy return unless @user globally_viewable = @subject.public? || (@subject.internal? && !@user.external?) - member = @subject.users_with_parents.include?(@user) - owner = @user.admin? || @subject.has_owner?(@user) - master = owner || @subject.has_master?(@user) + access_level = @subject.max_member_access_for_user(@user) + owner = access_level >= GroupMember::OWNER + master = access_level >= GroupMember::MASTER + reporter = access_level >= GroupMember::REPORTER can_read = false can_read ||= globally_viewable - can_read ||= member - can_read ||= @user.admin? + can_read ||= access_level >= GroupMember::GUEST can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? can! :read_group if can_read + if reporter + can! :admin_label + end + # Only group masters and group owners can create new projects if master can! :create_projects can! :admin_milestones - can! :admin_label end # Only group owner and administrators can admin group @@ -31,7 +34,7 @@ class GroupPolicy < BasePolicy can! :create_subgroup if @user.can_create_group end - if globally_viewable && @subject.request_access_enabled && !member + if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS can! :request_access end end diff --git a/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml b/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml new file mode 100644 index 00000000000..3b98525167d --- /dev/null +++ b/changelogs/unreleased/33154-permissions-for-project-labels-and-group-labels.yml @@ -0,0 +1,4 @@ +--- +title: Allow group reporters to manage group labels +merge_request: +author: diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 4c37a553227..a8331ceb5ff 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -9,11 +9,12 @@ describe GroupPolicy, models: true do let(:admin) { create(:admin) } let(:group) { create(:group) } + let(:reporter_permissions) { [:admin_label] } + let(:master_permissions) do [ :create_projects, - :admin_milestones, - :admin_label + :admin_milestones ] end @@ -42,6 +43,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.not_to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -52,6 +54,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.not_to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -62,6 +65,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -72,6 +76,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -82,6 +87,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -92,6 +98,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.to include(*master_permissions) is_expected.to include(*owner_permissions) end @@ -102,14 +109,27 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.to include(*master_permissions) is_expected.to include(*owner_permissions) end end - describe 'private nested group inherit permissions', :nested_groups do + describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do let(:nested_group) { create(:group, :private, parent: group) } + before do + nested_group.add_guest(guest) + nested_group.add_guest(reporter) + nested_group.add_guest(developer) + nested_group.add_guest(master) + + group.owners.destroy_all + + group.add_guest(owner) + nested_group.add_owner(owner) + end + subject { described_class.abilities(current_user, nested_group).to_set } context 'with no user' do @@ -117,6 +137,7 @@ describe GroupPolicy, models: true do it do is_expected.not_to include(:read_group) + is_expected.not_to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -127,6 +148,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.not_to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -137,6 +159,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -147,6 +170,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -157,6 +181,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.to include(*master_permissions) is_expected.not_to include(*owner_permissions) end @@ -167,6 +192,7 @@ describe GroupPolicy, models: true do it do is_expected.to include(:read_group) + is_expected.to include(*reporter_permissions) is_expected.to include(*master_permissions) is_expected.to include(*owner_permissions) end -- cgit v1.2.1 From 30f4f6b1fb8890e106883a84454121a56c9723e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Jun 2017 14:10:25 +0300 Subject: Fix link to ee code quality doc Signed-off-by: Dmitriy Zaporozhets --- doc/ci/examples/code_climate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index c6b9e2065cf..a047e809788 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -27,7 +27,7 @@ download and analyze the report artifact in JSON format. For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically extracted and shown right in the merge request widget. [Learn more on code quality -diffs in merge requests](../../user/project/merge_requests/code_quality_diff.md). +diffs in merge requests](http://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.md). [cli]: https://github.com/codeclimate/codeclimate [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor -- cgit v1.2.1 From 158581a447bb4976161eca26ddcb2fccd25888ab Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 14:18:24 +0100 Subject: Refactor the DeleteUserWorker --- app/controllers/registrations_controller.rb | 2 +- app/models/abuse_report.rb | 3 +-- app/models/spam_log.rb | 3 +-- app/models/user.rb | 5 +++++ app/services/users/destroy_service.rb | 17 ++++++++++++++++- lib/api/users.rb | 2 +- spec/controllers/registrations_controller_spec.rb | 2 +- spec/models/abuse_report_spec.rb | 4 +--- 8 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3ca14dee33c..cd2003586be 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -25,7 +25,7 @@ class RegistrationsController < Devise::RegistrationsController end def destroy - DeleteUserWorker.perform_async(current_user.id, current_user.id) + current_user.delete_async(deleted_by: current_user) respond_to do |format| format.html do diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 0d7c2d20029..4cbd90c5817 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -15,8 +15,7 @@ class AbuseReport < ActiveRecord::Base alias_method :author, :reporter def remove_user(deleted_by:) - user.block - DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true) + user.delete_async(deleted_by: deleted_by, params: { hard_delete: true }) end def notify diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index dd21ee15c6c..56a115d1db4 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -4,8 +4,7 @@ class SpamLog < ActiveRecord::Base validates :user, presence: true def remove_user(deleted_by:) - user.block - DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true) + user.delete_async(deleted_by: deleted_by, params: { hard_delete: true }) end def text diff --git a/app/models/user.rb b/app/models/user.rb index e6eb9d09656..9ed42d6b6f5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -809,6 +809,11 @@ class User < ActiveRecord::Base system_hook_service.execute_hooks_for(self, :destroy) end + def delete_async(deleted_by:, params: {}) + block if params[:hard_delete] + DeleteUserWorker.perform_async(deleted_by.id, id, params) + end + def notification_service NotificationService.new end diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 9eb6a600f6b..673afb8b5b9 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -6,12 +6,27 @@ module Users @current_user = current_user end + # Synchronously destroys +user+ + # + # The operation will fail if the user is the sole owner of any groups. To + # force the groups to be destroyed, pass `delete_solo_owned_groups: true` in + # +options+. + # + # The user's contributions will be migrated to a global ghost user. To + # force the contributions to be destroyed, pass `hard_delete: true` in + # +options+. + # + # `hard_delete: true` implies `delete_solo_owned_groups: true`. To perform + # a hard deletion without destroying solo-owned groups, pass + # `delete_solo_owned_groups: false, hard_delete: true` in +options+. def execute(user, options = {}) + delete_solo_owned_groups = options.fetch(:delete_solo_owned_groups, options[:hard_delete]) + unless Ability.allowed?(current_user, :destroy_user, user) raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!" end - if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present? + if !delete_solo_owned_groups && user.solo_owned_groups.present? user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' return user end diff --git a/lib/api/users.rb b/lib/api/users.rb index 2070dbd8bc7..e8694e90cf2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -293,7 +293,7 @@ module API user = User.find_by(id: params[:id]) not_found!('User') unless user - DeleteUserWorker.perform_async(current_user.id, user.id, hard_delete: params[:hard_delete]) + user.delete_async(deleted_by: current_user, params: params) end desc 'Block a user. Available only for admins.' diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 71dd9ef3eb4..634563fc290 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -77,7 +77,7 @@ describe RegistrationsController do end it 'schedules the user for destruction' do - expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id) + expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {}) post(:destroy) diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index ced93c8f762..90aec2b45e6 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -28,9 +28,7 @@ RSpec.describe AbuseReport, type: :model do end it 'lets a worker delete the user' do - expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, - delete_solo_owned_groups: true, - hard_delete: true) + expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, hard_delete: true) subject.remove_user(deleted_by: user) end -- cgit v1.2.1 From ff8a053d5ddf154cd52c3e21ac24619dbbee0dc7 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 15 May 2017 16:13:36 -0700 Subject: Fix Git over HTTP spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * The spec has 7 failures at this point * Specify rendered error messages * Render the GitAccess message rather than “Access denied” * Render the Not Found message provided by GitAccess, instead of a custom one * Expect GitAccess to check the config for whether Git-over-HTTP pull or push is disabled, rather than doing it in the controller * Add more thorough testing for authentication * Dried up a lot of tests * Fixed some broken tests --- lib/gitlab/checks/change_access.rb | 36 ++- lib/gitlab/git_access.rb | 11 +- lib/gitlab/git_access_wiki.rb | 6 +- spec/requests/git_http_spec.rb | 616 ++++++++++++++++++++++--------------- spec/support/git_http_helpers.rb | 19 +- 5 files changed, 419 insertions(+), 269 deletions(-) diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index c984eb20606..4b6f8abd61d 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -1,6 +1,20 @@ module Gitlab module Checks class ChangeAccess + ERROR_MESSAGES = { + push_code: 'You are not allowed to push code to this project.', + delete_default_branch: 'The default branch of a project cannot be deleted.', + force_push_protected_branch: 'You are not allowed to force push code to a protected branch on this project.', + non_master_delete_protected_branch: 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.', + non_web_delete_protected_branch: 'You can only delete protected branches using the web interface.', + merge_protected_branch: 'You are not allowed to merge code into protected branches on this project.', + push_protected_branch: 'You are not allowed to push code to protected branches on this project.', + change_existing_tags: 'You are not allowed to change existing tags on this project.', + update_protected_tag: 'Protected tags cannot be updated.', + delete_protected_tag: 'Protected tags cannot be deleted.', + create_protected_tag: 'You are not allowed to create this tag as it is protected.' + }.freeze + attr_reader :user_access, :project, :skip_authorization, :protocol def initialize( @@ -32,7 +46,7 @@ module Gitlab def push_checks if user_access.cannot_do_action?(:push_code) - "You are not allowed to push code to this project." + ERROR_MESSAGES[:push_code] end end @@ -40,7 +54,7 @@ module Gitlab return unless @branch_name if deletion? && @branch_name == project.default_branch - return "The default branch of a project cannot be deleted." + return ERROR_MESSAGES[:delete_default_branch] end protected_branch_checks @@ -50,7 +64,7 @@ module Gitlab return unless ProtectedBranch.protected?(project, @branch_name) if forced_push? - return "You are not allowed to force push code to a protected branch on this project." + return ERROR_MESSAGES[:force_push_protected_branch] end if deletion? @@ -62,22 +76,22 @@ module Gitlab def protected_branch_deletion_checks unless user_access.can_delete_branch?(@branch_name) - return 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.' + return ERROR_MESSAGES[:non_master_delete_protected_branch] end unless protocol == 'web' - 'You can only delete protected branches using the web interface.' + ERROR_MESSAGES[:non_web_delete_protected_branch] end end def protected_branch_push_checks if matching_merge_request? unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name) - "You are not allowed to merge code into protected branches on this project." + ERROR_MESSAGES[:merge_protected_branch] end else unless user_access.can_push_to_branch?(@branch_name) - "You are not allowed to push code to protected branches on this project." + ERROR_MESSAGES[:push_protected_branch] end end end @@ -86,7 +100,7 @@ module Gitlab return unless @tag_name if tag_exists? && user_access.cannot_do_action?(:admin_project) - return "You are not allowed to change existing tags on this project." + return ERROR_MESSAGES[:change_existing_tags] end protected_tag_checks @@ -95,11 +109,11 @@ module Gitlab def protected_tag_checks return unless ProtectedTag.protected?(project, @tag_name) - return "Protected tags cannot be updated." if update? - return "Protected tags cannot be deleted." if deletion? + return ERROR_MESSAGES[:update_protected_tag] if update? + return ERROR_MESSAGES[:delete_protected_tag] if deletion? unless user_access.can_create_tag?(@tag_name) - return "You are not allowed to create this tag as it is protected." + return ERROR_MESSAGES[:create_protected_tag] end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 99724db8da2..591f68cd415 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -9,7 +9,10 @@ module Gitlab download: 'You are not allowed to download code from this project.', deploy_key_upload: 'This deploy key does not have write access to this project.', - no_repo: 'A repository for this project does not exist yet.' + no_repo: 'A repository for this project does not exist yet.', + project_not_found: 'The project you were looking for could not be found.', + account_blocked: 'Your account has been blocked.', + command_not_allowed: "The command you're trying to execute is not allowed." }.freeze DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze @@ -73,19 +76,19 @@ module Gitlab return if deploy_key? if user && !user_access.allowed? - raise UnauthorizedError, "Your account has been blocked." + raise UnauthorizedError, ERROR_MESSAGES[:account_blocked] end end def check_project_accessibility! if project.blank? || !can_read_project? - raise UnauthorizedError, 'The project you were looking for could not be found.' + raise UnauthorizedError, ERROR_MESSAGES[:project_not_found] end end def check_command_existence!(cmd) unless ALL_COMMANDS.include?(cmd) - raise UnauthorizedError, "The command you're trying to execute is not allowed." + raise UnauthorizedError, ERROR_MESSAGES[:command_not_allowed] end end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 67eaa5e088d..dccafd2cae8 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,5 +1,9 @@ module Gitlab class GitAccessWiki < GitAccess + ERROR_MESSAGES = { + write_to_wiki: "You are not allowed to write to this project's wiki." + }.freeze + def guest_can_download_code? Guest.can?(:download_wiki_code, project) end @@ -12,7 +16,7 @@ module Gitlab if user_access.can_do_action?(:create_wiki) build_status_object(true) else - build_status_object(false, "You are not allowed to write to this project's wiki.") + build_status_object(false, ERROR_MESSAGES[:write_to_wiki]) end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 6ca3ef18fe6..080e2f12cd7 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -5,76 +5,217 @@ describe 'Git HTTP requests', lib: true do include WorkhorseHelpers include UserActivitiesHelpers - it "gives WWW-Authenticate hints" do - clone_get('doesnt/exist.git') + shared_examples 'pulls require Basic HTTP Authentication' do + context "when no credentials are provided" do + it "responds to downloads with status 401 Unauthorized (no project existence information leak)" do + download(path) do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + end + end - expect(response.header['WWW-Authenticate']).to start_with('Basic ') - end + context "when only username is provided" do + it "responds to downloads with status 401 Unauthorized" do + download(path, user: user.username) do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + end + end - describe "User with no identities" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository, path: 'project.git-project') } + context "when username and password are provided" do + context "when authentication fails" do + it "responds to downloads with status 401 Unauthorized" do + download(path, user: user.username, password: "wrong-password") do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + end + end - context "when the project doesn't exist" do - context "when no authentication is provided" do - it "responds with status 401 (no project existence information leak)" do - download('doesnt/exist.git') do |response| - expect(response).to have_http_status(401) + context "when authentication succeeds" do + it "does not respond to downloads with status 401 Unauthorized" do + download(path, user: user.username, password: user.password) do |response| + expect(response).not_to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to be_nil end end end + end + end - context "when username and password are provided" do - context "when authentication fails" do - it "responds with status 401" do - download('doesnt/exist.git', user: user.username, password: "nope") do |response| - expect(response).to have_http_status(401) - end + shared_examples 'pushes require Basic HTTP Authentication' do + context "when no credentials are provided" do + it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do + upload(path) do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + end + end + + context "when only username is provided" do + it "responds to uploads with status 401 Unauthorized" do + upload(path, user: user.username) do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + end + end + + context "when username and password are provided" do + context "when authentication fails" do + it "responds to uploads with status 401 Unauthorized" do + upload(path, user: user.username, password: "wrong-password") do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end + end - context "when authentication succeeds" do - it "responds with status 404" do - download('/doesnt/exist.git', user: user.username, password: user.password) do |response| - expect(response).to have_http_status(404) - end + context "when authentication succeeds" do + it "does not respond to uploads with status 401 Unauthorized" do + upload(path, user: user.username, password: user.password) do |response| + expect(response).not_to have_http_status(:unauthorized) + expect(response.header['WWW-Authenticate']).to be_nil end end end end + end + + shared_examples_for 'pulls are allowed' do + it do + download(path, env) do |response| + expect(response).to have_http_status(:ok) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + end + end + + shared_examples_for 'pushes are allowed' do + it do + upload(path, env) do |response| + expect(response).to have_http_status(:ok) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + end + end + + describe "User with no identities" do + let(:user) { create(:user) } - context "when the Wiki for a project exists" do - it "responds with the right project" do - wiki = ProjectWiki.new(project) - project.update_attribute(:visibility_level, Project::PUBLIC) + context "when the project doesn't exist" do + let(:path) { 'doesnt/exist.git' } - download("/#{wiki.repository.path_with_namespace}.git") do |response| - json_body = ActiveSupport::JSON.decode(response.body) + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' - expect(response).to have_http_status(200) - expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + context 'when authenticated' do + it 'rejects downloads and uploads with 404 Not Found' do + download_or_upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:not_found) + end end end + end + + context "when requesting the Wiki" do + let(:wiki) { ProjectWiki.new(project) } + let(:path) { "/#{wiki.repository.path_with_namespace}.git" } + + context "when the project is public" do + let(:project) { create(:project, :repository, :public, :wiki_enabled) } + + it_behaves_like 'pushes require Basic HTTP Authentication' - context 'but the repo is disabled' do - let(:project) { create(:project, :repository_disabled, :wiki_enabled) } - let(:wiki) { ProjectWiki.new(project) } - let(:path) { "/#{wiki.repository.path_with_namespace}.git" } + context 'when unauthenticated' do + let(:env) { {} } - before do - project.team << [user, :developer] + it_behaves_like 'pulls are allowed' + + it "responds to pulls with the wiki's repo" do + download(path) do |response| + json_body = ActiveSupport::JSON.decode(response.body) + + expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + end + end end - it 'allows clones' do - download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(200) + context 'when authenticated' do + let(:env) { { user: user.username, password: user.password } } + + context 'and as a developer on the team' do + before do + project.team << [user, :developer] + end + + context 'but the repo is disabled' do + let(:project) { create(:project, :repository, :public, :repository_disabled, :wiki_enabled) } + + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' + end + end + + context 'and not on the team' do + it_behaves_like 'pulls are allowed' + + it 'rejects pushes with 403 Forbidden' do + upload(path, env) do |response| + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_wiki_error(:write_to_wiki)) + end + end end end + end - it 'allows pushes' do - upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(200) + context "when the project is private" do + let(:project) { create(:project, :repository, :private, :wiki_enabled) } + + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' + + context 'when authenticated' do + context 'and as a developer on the team' do + before do + project.team << [user, :developer] + end + + context 'but the repo is disabled' do + let(:project) { create(:project, :repository, :private, :repository_disabled, :wiki_enabled) } + + it 'allows clones' do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:ok) + end + end + + it 'pushes are allowed' do + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:ok) + end + end + end + end + + context 'and not on the team' do + it 'rejects clones with 404 Not Found' do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:not_found) + expect(response.body).to eq(git_access_error(:project_not_found)) + end + end + + it 'rejects pushes with 404 Not Found' do + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:not_found) + expect(response.body).to eq(git_access_error(:project_not_found)) + end + end end end end @@ -84,49 +225,60 @@ describe 'Git HTTP requests', lib: true do let(:path) { "#{project.path_with_namespace}.git" } context "when the project is public" do - before do - project.update_attribute(:visibility_level, Project::PUBLIC) - end + let(:project) { create(:project, :repository, :public) } - it "downloads get status 200" do - download(path, {}) do |response| - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - end - end + it_behaves_like 'pushes require Basic HTTP Authentication' - it "uploads get status 401" do - upload(path, {}) do |response| - expect(response).to have_http_status(401) - end + context 'when not authenticated' do + let(:env) { {} } + + it_behaves_like 'pulls are allowed' end - context "with correct credentials" do + context "when authenticated" do let(:env) { { user: user.username, password: user.password } } - it "uploads get status 403" do - upload(path, env) do |response| - expect(response).to have_http_status(403) + context 'as a developer on the team' do + before do + project.team << [user, :developer] end - end - context 'but git-receive-pack is disabled' do - it "responds with status 404" do - allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' - upload(path, env) do |response| - expect(response).to have_http_status(403) + context 'but git-receive-pack over HTTP is disabled in config' do + before do + allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) + end + + it 'rejects pushes with 403 Forbidden' do + upload(path, env) do |response| + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_error(:receive_pack_disabled_in_config)) + end + end + end + + context 'but git-upload-pack over HTTP is disabled in config' do + it "rejects pushes with 403 Forbidden" do + allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + + download(path, env) do |response| + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_error(:upload_pack_disabled_in_config)) + end end end end - end - context 'but git-upload-pack is disabled' do - it "responds with status 404" do - allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + context 'and not a member of the team' do + it_behaves_like 'pulls are allowed' - download(path, {}) do |response| - expect(response).to have_http_status(404) + it 'rejects pushes with 403 Forbidden' do + upload(path, env) do |response| + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(change_access_error(:push_code)) + end end end end @@ -141,66 +293,41 @@ describe 'Git HTTP requests', lib: true do context 'when the repo is public' do context 'but the repo is disabled' do - it 'does not allow to clone the repo' do - project = create(:project, :public, :repository_disabled) + let(:project) { create(:project, :public, :repository, :repository_disabled) } + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { {} } - download("#{project.path_with_namespace}.git", {}) do |response| - expect(response).to have_http_status(:unauthorized) - end - end + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' end context 'but the repo is enabled' do - it 'allows to clone the repo' do - project = create(:project, :public, :repository_enabled) + let(:project) { create(:project, :public, :repository, :repository_enabled) } + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { {} } - download("#{project.path_with_namespace}.git", {}) do |response| - expect(response).to have_http_status(:ok) - end - end + it_behaves_like 'pulls are allowed' end context 'but only project members are allowed' do - it 'does not allow to clone the repo' do - project = create(:project, :public, :repository_private) + let(:project) { create(:project, :public, :repository, :repository_private) } - download("#{project.path_with_namespace}.git", {}) do |response| - expect(response).to have_http_status(:unauthorized) - end - end + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' end end end context "when the project is private" do - before do - project.update_attribute(:visibility_level, Project::PRIVATE) - end + let(:project) { create(:project, :repository, :private) } - context "when no authentication is provided" do - it "responds with status 401 to downloads" do - download(path, {}) do |response| - expect(response).to have_http_status(401) - end - end - - it "responds with status 401 to uploads" do - upload(path, {}) do |response| - expect(response).to have_http_status(401) - end - end - end + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' context "when username and password are provided" do let(:env) { { user: user.username, password: 'nope' } } context "when authentication fails" do - it "responds with status 401" do - download(path, env) do |response| - expect(response).to have_http_status(401) - end - end - context "when the user is IP banned" do it "responds with status 401" do expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) @@ -208,7 +335,7 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) - expect(response).to have_http_status(401) + expect(response).to have_http_status(:unauthorized) end end end @@ -222,37 +349,39 @@ describe 'Git HTTP requests', lib: true do end context "when the user is blocked" do - it "responds with status 401" do + it "rejects pulls with 401 Unauthorized" do user.block project.team << [user, :master] download(path, env) do |response| - expect(response).to have_http_status(401) + expect(response).to have_http_status(:unauthorized) end end - it "responds with status 401 for unknown projects (no project existence information leak)" do + it "rejects pulls with 401 Unauthorized for unknown projects (no project existence information leak)" do user.block download('doesnt/exist.git', env) do |response| - expect(response).to have_http_status(401) + expect(response).to have_http_status(:unauthorized) end end end context "when the user isn't blocked" do - it "downloads get status 200" do - expect(Rack::Attack::Allow2Ban).to receive(:reset) - - clone_get(path, env) + it "resets the IP in Rack Attack on download" do + expect(Rack::Attack::Allow2Ban).to receive(:reset).twice - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + download(path, env) do + expect(response).to have_http_status(:ok) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end end - it "uploads get status 200" do - upload(path, env) do |response| - expect(response).to have_http_status(200) + it "resets the IP in Rack Attack on upload" do + expect(Rack::Attack::Allow2Ban).to receive(:reset).twice + + upload(path, env) do + expect(response).to have_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -272,56 +401,43 @@ describe 'Git HTTP requests', lib: true do @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") end - it "downloads get status 200" do - clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token - - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - end - - it "uploads get status 200" do - push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { { user: 'oauth2', password: @token.token } } - expect(response).to have_http_status(200) - end + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' end context 'when user has 2FA enabled' do let(:user) { create(:user, :two_factor) } let(:access_token) { create(:personal_access_token, user: user) } + let(:path) { "#{project.path_with_namespace}.git" } before do project.team << [user, :master] end context 'when username and password are provided' do - it 'rejects the clone attempt' do - download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response| - expect(response).to have_http_status(401) + it 'rejects pulls with 2FA error message' do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:unauthorized) expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') end end it 'rejects the push attempt' do - upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response| - expect(response).to have_http_status(401) + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:unauthorized) expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') end end end context 'when username and personal access token are provided' do - it 'allows clones' do - download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response| - expect(response).to have_http_status(200) - end - end + let(:env) { { user: user.username, password: access_token.token } } - it 'allows pushes' do - upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response| - expect(response).to have_http_status(200) - end - end + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' end end @@ -357,15 +473,15 @@ describe 'Git HTTP requests', lib: true do end context "when the user doesn't have access to the project" do - it "downloads get status 404" do + it "pulls get status 404" do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(404) + expect(response).to have_http_status(:not_found) end end it "uploads get status 404" do upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(404) + expect(response).to have_http_status(:not_found) end end end @@ -378,23 +494,24 @@ describe 'Git HTTP requests', lib: true do let(:other_project) { create(:empty_project) } context 'when build created by system is authenticated' do - it "downloads get status 200" do - clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { { user: 'gitlab-ci-token', password: build.token } } - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - end + it_behaves_like 'pulls are allowed' - it "uploads get status 401 (no project existence information leak)" do - push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + # TODO Verify this is desired behavior + it "rejects pushes with 401 Unauthorized (no project existence information leak)" do + push_get(path, env) - expect(response).to have_http_status(401) + expect(response).to have_http_status(:unauthorized) end - it "downloads from other project get status 404" do - clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + # TODO Verify this is desired behavior. Should be 403 Forbidden? + it "rejects pulls for other project with 404 Not Found" do + clone_get("#{other_project.path_with_namespace}.git", env) - expect(response).to have_http_status(404) + expect(response).to have_http_status(:not_found) + expect(response.body).to eq('TODO: What should this be?') end end @@ -412,7 +529,7 @@ describe 'Git HTTP requests', lib: true do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(200) + expect(response).to have_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end @@ -423,13 +540,13 @@ describe 'Git HTTP requests', lib: true do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(403) + expect(response).to have_http_status(:forbidden) end it 'uploads get status 403' do push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(401) + expect(response).to have_http_status(:unauthorized) end end @@ -441,7 +558,7 @@ describe 'Git HTTP requests', lib: true do it 'downloads from other project get status 403' do clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(403) + expect(response).to have_http_status(:forbidden) end end @@ -453,91 +570,93 @@ describe 'Git HTTP requests', lib: true do it 'downloads from other project get status 404' do clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(404) + expect(response).to have_http_status(:not_found) end end end end end - end - context "when the project path doesn't end in .git" do - context "GET info/refs" do - let(:path) { "/#{project.path_with_namespace}/info/refs" } + context "when the project path doesn't end in .git" do + let(:project) { create(:project, :repository, :public, path: 'project.git-project') } + + context "GET info/refs" do + let(:path) { "/#{project.path_with_namespace}/info/refs" } - context "when no params are added" do - before { get path } + context "when no params are added" do + before { get path } - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs") + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs") + end end - end - context "when the upload-pack service is requested" do - let(:params) { { service: 'git-upload-pack' } } - before { get path, params } + context "when the upload-pack service is requested" do + let(:params) { { service: 'git-upload-pack' } } + before { get path, params } - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + end end - end - context "when the receive-pack service is requested" do - let(:params) { { service: 'git-receive-pack' } } - before { get path, params } + context "when the receive-pack service is requested" do + let(:params) { { service: 'git-receive-pack' } } + before { get path, params } - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + end end - end - context "when the params are anything else" do - let(:params) { { service: 'git-implode-pack' } } - before { get path, params } + context "when the params are anything else" do + let(:params) { { service: 'git-implode-pack' } } + before { get path, params } - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) + end end end - end - context "POST git-upload-pack" do - it "fails to find a route" do - expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + context "POST git-upload-pack" do + it "fails to find a route" do + expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + end end - end - context "POST git-receive-pack" do - it "failes to find a route" do - expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + context "POST git-receive-pack" do + it "failes to find a route" do + expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + end end end - end - context "retrieving an info/refs file" do - before { project.update_attribute(:visibility_level, Project::PUBLIC) } + context "retrieving an info/refs file" do + let(:project) { create(:project, :repository, :public) } + + context "when the file exists" do + before do + # Provide a dummy file in its place + allow_any_instance_of(Repository).to receive(:blob_at).and_call_original + allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do + Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt') + end - context "when the file exists" do - before do - # Provide a dummy file in its place - allow_any_instance_of(Repository).to receive(:blob_at).and_call_original - allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do - Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt') + get "/#{project.path_with_namespace}/blob/master/info/refs" end - get "/#{project.path_with_namespace}/blob/master/info/refs" + it "returns the file" do + expect(response).to have_http_status(:ok) + end end - it "returns the file" do - expect(response).to have_http_status(200) - end - end + context "when the file does not exist" do + before { get "/#{project.path_with_namespace}/blob/master/info/refs" } - context "when the file does not exist" do - before { get "/#{project.path_with_namespace}/blob/master/info/refs" } - - it "returns not found" do - expect(response).to have_http_status(404) + it "returns not found" do + expect(response).to have_http_status(:not_found) + end end end end @@ -546,6 +665,7 @@ describe 'Git HTTP requests', lib: true do describe "User with LDAP identity" do let(:user) { create(:omniauth_user, extern_uid: dn) } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } + let(:path) { 'doesnt/exist.git' } before do allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) @@ -553,44 +673,36 @@ describe 'Git HTTP requests', lib: true do allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user) end - context "when authentication fails" do - context "when no authentication is provided" do - it "responds with status 401" do - download('doesnt/exist.git') do |response| - expect(response).to have_http_status(401) - end - end - end - - context "when username and invalid password are provided" do - it "responds with status 401" do - download('doesnt/exist.git', user: user.username, password: "nope") do |response| - expect(response).to have_http_status(401) - end - end - end - end + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' context "when authentication succeeds" do context "when the project doesn't exist" do - it "responds with status 404" do - download('/doesnt/exist.git', user: user.username, password: user.password) do |response| - expect(response).to have_http_status(404) + it "responds with status 404 Not Found" do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(:not_found) end end end context "when the project exists" do - let(:project) { create(:project, path: 'project.git-project') } + let(:project) { create(:project, :repository) } + let(:path) { "#{project.full_path}.git" } + let(:env) { { user: user.username, password: user.password } } - before do - project.team << [user, :master] - end + context 'and the user is on the team' do + before do + project.team << [user, :master] + end - it "responds with status 200" do - clone_get(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(200) + it "responds with status 200" do + clone_get(path, env) do |response| + expect(response).to have_http_status(200) + end end + + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' end end end diff --git a/spec/support/git_http_helpers.rb b/spec/support/git_http_helpers.rb index 46b686fce94..d3d51560a9d 100644 --- a/spec/support/git_http_helpers.rb +++ b/spec/support/git_http_helpers.rb @@ -35,9 +35,14 @@ module GitHttpHelpers yield response end + def download_or_upload(*args, &block) + download(*args, &block) + upload(*args, &block) + end + def auth_env(user, password, spnego_request_token) env = workhorse_internal_api_request_header - if user && password + if user env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) elsif spnego_request_token env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}" @@ -45,4 +50,16 @@ module GitHttpHelpers env end + + def git_access_error(error_key) + Gitlab::GitAccess::ERROR_MESSAGES[error_key] + end + + def git_access_wiki_error(error_key) + Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] + end + + def change_access_error(error_key) + Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] + end end -- cgit v1.2.1 From 2d6cafa781ae24586fcd5307ae01daf3f407aa25 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 15 May 2017 16:19:36 -0700 Subject: Render GitAccess message if authorized --- app/controllers/projects/git_http_controller.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 9e4edcae101..a36dc362f4e 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -68,17 +68,13 @@ class Projects::GitHttpController < Projects::GitHttpClientController def render_denied if user && can?(user, :read_project, project) - render plain: access_denied_message, status: :forbidden + render plain: access_check.message, status: :forbidden else # Do not leak information about project existence render_not_found end end - def access_denied_message - 'Access denied' - end - def upload_pack_allowed? return false unless Gitlab.config.gitlab_shell.upload_pack -- cgit v1.2.1 From a738a446f4ade6204c10f016e355da354dbfc01f Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 16 May 2017 09:02:52 -0700 Subject: Check disabled commands in GitAccess instead --- app/controllers/projects/git_http_controller.rb | 4 --- lib/gitlab/git_access.rb | 27 +++++++++++++++- spec/lib/gitlab/git_access_spec.rb | 43 ++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index a36dc362f4e..e7b498599f2 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -76,8 +76,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def upload_pack_allowed? - return false unless Gitlab.config.gitlab_shell.upload_pack - access_check.allowed? || ci? end @@ -96,8 +94,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def receive_pack_allowed? - return false unless Gitlab.config.gitlab_shell.receive_pack - access_check.allowed? end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 591f68cd415..1d052ac9b33 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -12,7 +12,9 @@ module Gitlab no_repo: 'A repository for this project does not exist yet.', project_not_found: 'The project you were looking for could not be found.', account_blocked: 'Your account has been blocked.', - command_not_allowed: "The command you're trying to execute is not allowed." + command_not_allowed: "The command you're trying to execute is not allowed.", + upload_pack_disabled_in_config: 'The command "git-upload-pack" is not allowed.', + receive_pack_disabled_in_config: 'The command "git-receive-pack" is not allowed.' }.freeze DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze @@ -33,6 +35,7 @@ module Gitlab check_protocol! check_active_user! check_project_accessibility! + check_command_disabled!(cmd) check_command_existence!(cmd) check_repository_existence! @@ -86,6 +89,16 @@ module Gitlab end end + def check_command_disabled!(cmd) + if http? + if upload_pack?(cmd) && !Gitlab.config.gitlab_shell.upload_pack + raise UnauthorizedError, ERROR_MESSAGES[:upload_pack_disabled_in_config] + elsif receive_pack?(cmd) && !Gitlab.config.gitlab_shell.receive_pack + raise UnauthorizedError, ERROR_MESSAGES[:receive_pack_disabled_in_config] + end + end + end + def check_command_existence!(cmd) unless ALL_COMMANDS.include?(cmd) raise UnauthorizedError, ERROR_MESSAGES[:command_not_allowed] @@ -179,6 +192,18 @@ module Gitlab end || Guest.can?(:read_project, project) end + def http? + protocol == 'http' + end + + def upload_pack?(command) + command == 'git-upload-pack' + end + + def receive_pack?(command) + command == 'git-receive-pack' + end + protected def user diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 25769977f24..a86afe57873 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,10 +1,11 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do - let(:access) { Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities) } + let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:actor) { user } + let(:protocol) { 'ssh' } let(:authentication_abilities) do [ :read_project, @@ -50,6 +51,46 @@ describe Gitlab::GitAccess, lib: true do end end + describe '#check with commands disabled' do + before { project.team << [user, :master] } + + context 'over http' do + let(:protocol) { 'http' } + + context 'when the git-upload-pack command is disabled in config' do + before do + allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + end + + context 'when calling git-upload-pack' do + subject { access.check('git-upload-pack', '_any') } + it { expect(subject.allowed?).to be_falsey } + it { expect(subject.message).to eq('The command "git-upload-pack" is not allowed.') } + end + + context 'when calling git-receive-pack' do + it { expect(access.check('git-receive-pack', '_any').allowed?).to be_truthy } + end + end + + context 'when the git-receive-pack command is disabled in config' do + before do + allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) + end + + context 'when calling git-receive-pack' do + subject { access.check('git-receive-pack', '_any') } + it { expect(subject.allowed?).to be_falsey } + it { expect(subject.message).to eq('The command "git-receive-pack" is not allowed.') } + end + + context 'when calling git-upload-pack' do + it { expect(access.check('git-upload-pack', '_any').allowed?).to be_truthy } + end + end + end + end + describe '#check_download_access!' do subject { access.check('git-upload-pack', '_any') } -- cgit v1.2.1 From b387429458f77a3608e077dfe2d50b0a313f8832 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 16 May 2017 09:08:23 -0700 Subject: Refactor --- app/controllers/projects/git_http_client_controller.rb | 4 ---- spec/lib/gitlab/git_access_spec.rb | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 9a1bf037a95..44b0853e3e9 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -152,8 +152,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController def has_authentication_ability?(capability) (authentication_abilities || []).include?(capability) end - - def authentication_project - authentication_result.project - end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index a86afe57873..1033fef9684 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -23,30 +23,30 @@ describe Gitlab::GitAccess, lib: true do context 'ssh disabled' do before do disable_protocol('ssh') - @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities) end it 'blocks ssh git push' do - expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey + expect(access.check('git-receive-pack', '_any').allowed?).to be_falsey end it 'blocks ssh git pull' do - expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey + expect(access.check('git-upload-pack', '_any').allowed?).to be_falsey end end context 'http disabled' do + let(:protocol) { 'http' } + before do disable_protocol('http') - @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities) end it 'blocks http push' do - expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey + expect(access.check('git-receive-pack', '_any').allowed?).to be_falsey end it 'blocks http git pull' do - expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey + expect(access.check('git-upload-pack', '_any').allowed?).to be_falsey end end end -- cgit v1.2.1 From bad08fbea2a32655a6d87f2140840c317cea6c80 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 16 May 2017 12:58:46 -0700 Subject: Move CI access logic into GitAccess --- app/controllers/concerns/lfs_request.rb | 4 + .../projects/git_http_client_controller.rb | 20 +--- app/controllers/projects/git_http_controller.rb | 16 ++- lib/gitlab/git_access.rb | 20 +++- spec/lib/gitlab/git_access_spec.rb | 128 ++++++++++++++++++++- spec/requests/git_http_spec.rb | 58 ++++++---- spec/support/git_http_helpers.rb | 9 +- 7 files changed, 198 insertions(+), 57 deletions(-) diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index ae91e02488a..2b6afaa6233 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -106,4 +106,8 @@ module LfsRequest def objects @objects ||= (params[:objects] || []).to_a end + + def has_authentication_ability?(capability) + (authentication_abilities || []).include?(capability) + end end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 44b0853e3e9..7f3205a8001 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -128,28 +128,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController @authentication_result = Gitlab::Auth.find_for_git_client( login, password, project: project, ip: request.ip) - return false unless @authentication_result.success? - - if download_request? - authentication_has_download_access? - else - authentication_has_upload_access? - end + @authentication_result.success? end def ci? authentication_result.ci?(project) end - - def authentication_has_download_access? - has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) - end - - def authentication_has_upload_access? - has_authentication_ability?(:push_code) - end - - def has_authentication_ability?(capability) - (authentication_abilities || []).include?(capability) - end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index e7b498599f2..2c2766cf623 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -67,20 +67,24 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def render_denied - if user && can?(user, :read_project, project) - render plain: access_check.message, status: :forbidden + if access_check.message == Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found] + render plain: access_check.message, status: :not_found else - # Do not leak information about project existence - render_not_found + render plain: access_check.message, status: :forbidden end end def upload_pack_allowed? - access_check.allowed? || ci? + access_check.allowed? end def access - @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities) + @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities) + end + + def access_actor + return user if user + return :ci if ci? end def access_check diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 1d052ac9b33..1ffac5385c2 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -63,6 +63,11 @@ module Gitlab authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) end + # Allow generic CI (build without a user) for backwards compatibility + def ci_can_download_code? + authentication_abilities.include?(:build_download_code) && ci? + end + def protocol_allowed? Gitlab::ProtocolAccess.allowed?(protocol) end @@ -115,6 +120,7 @@ module Gitlab return if deploy_key? passed = user_can_download_code? || + ci_can_download_code? || build_can_download_code? || guest_can_download_code? @@ -184,11 +190,17 @@ module Gitlab actor.is_a?(DeployKey) end + def ci? + actor == :ci + end + def can_read_project? - if deploy_key + if deploy_key? deploy_key.has_access_to?(project) elsif user user.can?(:read_project, project) + elsif ci? + true # allow CI (build without a user) for backwards compatibility end || Guest.can?(:read_project, project) end @@ -213,10 +225,12 @@ module Gitlab case actor when User actor - when DeployKey - nil when Key actor.user + when DeployKey + nil + when :ci + nil end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 1033fef9684..0efe15856fc 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do + let(:pull_access_check) { access.check('git-upload-pack', '_any') } + let(:push_access_check) { access.check('git-receive-pack', '_any') } let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } @@ -51,7 +53,123 @@ describe Gitlab::GitAccess, lib: true do end end - describe '#check with commands disabled' do + describe '#check_project_accessibility!' do + context 'when the project exists' do + context 'when actor exists' do + context 'when actor is a DeployKey' do + let(:deploy_key) { create(:deploy_key, user: user, can_push: true) } + let(:actor) { deploy_key } + + context 'when the DeployKey has access to the project' do + before { deploy_key.projects << project } + + it 'allows pull access' do + expect(pull_access_check.allowed?).to be_truthy + end + + it 'allows push access' do + expect(push_access_check.allowed?).to be_truthy + end + end + + context 'when the Deploykey does not have access to the project' do + it 'blocks pulls with "not found"' do + expect(pull_access_check.allowed?).to be_falsey + expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + end + + it 'blocks pushes with "not found"' do + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('The project you were looking for could not be found.') + end + end + end + + context 'when actor is a User' do + context 'when the User can read the project' do + before { project.team << [user, :master] } + + it 'allows pull access' do + expect(pull_access_check.allowed?).to be_truthy + end + + it 'allows push access' do + expect(push_access_check.allowed?).to be_truthy + end + end + + context 'when the User cannot read the project' do + it 'blocks pulls with "not found"' do + expect(pull_access_check.allowed?).to be_falsey + expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + end + + it 'blocks pushes with "not found"' do + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('The project you were looking for could not be found.') + end + end + end + + # For backwards compatibility + context 'when actor is :ci' do + let(:actor) { :ci } + let(:authentication_abilities) { build_authentication_abilities } + + it 'allows pull access' do + expect(pull_access_check.allowed?).to be_truthy + end + + it 'does not block pushes with "not found"' do + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('You are not allowed to upload code for this project.') + end + end + end + + context 'when actor is nil' do + let(:actor) { nil } + + context 'when guests can read the project' do + let(:project) { create(:project, :repository, :public) } + + it 'allows pull access' do + expect(pull_access_check.allowed?).to be_truthy + end + + it 'does not block pushes with "not found"' do + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('You are not allowed to upload code for this project.') + end + end + + context 'when guests cannot read the project' do + it 'blocks pulls with "not found"' do + expect(pull_access_check.allowed?).to be_falsey + expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + end + + it 'blocks pushes with "not found"' do + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('The project you were looking for could not be found.') + end + end + end + end + + context 'when the project is nil' do + let(:project) { nil } + + it 'blocks any command with "not found"' do + expect(pull_access_check.allowed?).to be_falsey + expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + expect(push_access_check.allowed?).to be_falsey + expect(push_access_check.message).to eq('The project you were looking for could not be found.') + end + end + end + + describe '#check_command_disabled!' do before { project.team << [user, :master] } context 'over http' do @@ -219,6 +337,14 @@ describe Gitlab::GitAccess, lib: true do end end end + + describe 'generic CI (build without a user)' do + let(:actor) { :ci } + + context 'pull code' do + it { expect(subject).to be_allowed } + end + end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 080e2f12cd7..ab7c56fcdf0 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -489,29 +489,41 @@ describe 'Git HTTP requests', lib: true do end context "when a gitlab ci token is provided" do + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, :running) } - let(:project) { build.project } let(:other_project) { create(:empty_project) } + before do + build.update!(project: project) # can't associate it on factory create + end + context 'when build created by system is authenticated' do let(:path) { "#{project.path_with_namespace}.git" } let(:env) { { user: 'gitlab-ci-token', password: build.token } } it_behaves_like 'pulls are allowed' - # TODO Verify this is desired behavior - it "rejects pushes with 401 Unauthorized (no project existence information leak)" do + # A non-401 here is not an information leak since the system is + # "authenticated" as CI using the correct token. It does not have + # push access, so pushes should be rejected as forbidden, and giving + # a reason is fine. + # + # We know for sure it is not an information leak since pulls using + # the build token must be allowed. + it "rejects pushes with 403 Forbidden" do push_get(path, env) - expect(response).to have_http_status(:unauthorized) + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_error(:upload)) end - # TODO Verify this is desired behavior. Should be 403 Forbidden? + # We are "authenticated" as CI using a valid token here. But we are + # not authorized to see any other project, so return "not found". it "rejects pulls for other project with 404 Not Found" do clone_get("#{other_project.path_with_namespace}.git", env) expect(response).to have_http_status(:not_found) - expect(response.body).to eq('TODO: What should this be?') + expect(response.body).to eq(git_access_error(:project_not_found)) end end @@ -522,31 +534,27 @@ describe 'Git HTTP requests', lib: true do end shared_examples 'can download code only' do - it 'downloads get status 200' do - allow_any_instance_of(Repository). - to receive(:exists?).and_return(true) - - clone_get "#{project.path_with_namespace}.git", - user: 'gitlab-ci-token', password: build.token + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { { user: 'gitlab-ci-token', password: build.token } } - expect(response).to have_http_status(:ok) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - end - - it 'downloads from non-existing repository and gets 403' do - allow_any_instance_of(Repository). - to receive(:exists?).and_return(false) + it_behaves_like 'pulls are allowed' - clone_get "#{project.path_with_namespace}.git", - user: 'gitlab-ci-token', password: build.token + context 'when the repo does not exist' do + let(:project) { create(:empty_project) } + + it 'rejects pulls with 403 Forbidden' do + clone_get path, env - expect(response).to have_http_status(:forbidden) + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_error(:no_repo)) + end end - it 'uploads get status 403' do - push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + it 'rejects pushes with 403 Forbidden' do + push_get path, env - expect(response).to have_http_status(:unauthorized) + expect(response).to have_http_status(:forbidden) + expect(response.body).to eq(git_access_error(:upload)) end end diff --git a/spec/support/git_http_helpers.rb b/spec/support/git_http_helpers.rb index d3d51560a9d..b8289e6c5f1 100644 --- a/spec/support/git_http_helpers.rb +++ b/spec/support/git_http_helpers.rb @@ -52,14 +52,17 @@ module GitHttpHelpers end def git_access_error(error_key) - Gitlab::GitAccess::ERROR_MESSAGES[error_key] + message = Gitlab::GitAccess::ERROR_MESSAGES[error_key] + message || raise("GitAccess error message key '#{error_key}' not found") end def git_access_wiki_error(error_key) - Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] + message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] + message || raise("GitAccessWiki error message key '#{error_key}' not found") end def change_access_error(error_key) - Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] + message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] + message || raise("ChangeAccess error message key '#{error_key}' not found") end end -- cgit v1.2.1 From 9d78f83571e7dbfbab889102b497ada7c02f409d Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Wed, 17 May 2017 11:00:36 -0700 Subject: Specify new Git-LFS-over-HTTP behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes to `GitHttpClientController`’s authentication logic caused this behavior change. The old 401 Unauthorized statuses didn’t cause any harm, but they weren’t quite as accurate as the new behavior. --- spec/requests/lfs_http_spec.rb | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 0c9b4121adf..697b150ab34 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -759,8 +759,8 @@ describe 'Git LFS API and storage' do context 'tries to push to own project' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end @@ -769,8 +769,9 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 401' do - expect(response).to have_http_status(401) + # I'm not sure what this tests that is different from the previous test + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end end @@ -778,8 +779,8 @@ describe 'Git LFS API and storage' do context 'does not have user' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end end @@ -979,8 +980,8 @@ describe 'Git LFS API and storage' do put_authorize end - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403 (not 404 because the build user can read the project)' do + expect(response).to have_http_status(403) end end @@ -993,8 +994,8 @@ describe 'Git LFS API and storage' do put_authorize end - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 404 (do not leak non-public project existence)' do + expect(response).to have_http_status(404) end end end @@ -1006,8 +1007,8 @@ describe 'Git LFS API and storage' do put_authorize end - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 404 (do not leak non-public project existence)' do + expect(response).to have_http_status(404) end end end @@ -1079,8 +1080,8 @@ describe 'Git LFS API and storage' do context 'tries to push to own project' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end @@ -1089,8 +1090,9 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 401' do - expect(response).to have_http_status(401) + # I'm not sure what this tests that is different from the previous test + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end end @@ -1098,8 +1100,8 @@ describe 'Git LFS API and storage' do context 'does not have user' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403 (not 404 because project is public)' do + expect(response).to have_http_status(403) end end end -- cgit v1.2.1 From 957edb13fdb21e21efbc68fc342209f4b53a66e4 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Wed, 17 May 2017 15:47:59 -0700 Subject: Refactor to let `GitAccess` check protocol config This already works due to previous refactoring. --- app/controllers/projects/git_http_controller.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 2c2766cf623..073c76933c1 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -10,8 +10,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController render_ok elsif receive_pack? && receive_pack_allowed? render_ok - elsif http_blocked? - render_http_not_allowed else render_denied end @@ -62,10 +60,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) end - def render_http_not_allowed - render plain: access_check.message, status: :forbidden - end - def render_denied if access_check.message == Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found] render plain: access_check.message, status: :not_found @@ -93,10 +87,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController @access_check ||= access.check(git_command, '_any') end - def http_blocked? - !access.protocol_allowed? - end - def receive_pack_allowed? access_check.allowed? end -- cgit v1.2.1 From 23d37382dabe3f7c7f2e11df2731de8e939e0cab Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Fri, 19 May 2017 12:58:45 -0700 Subject: Refactor to let GitAccess errors bubble up No external behavior change. This allows `GitHttpController` to set the HTTP status based on the type of error. Alternatively, we could have added an attribute to GitAccessStatus, but this pattern seemed appropriate. --- app/controllers/projects/git_http_controller.rb | 49 ++----- lib/api/internal.rb | 38 ++--- lib/gitlab/checks/change_access.rb | 32 ++-- lib/gitlab/git_access.rb | 9 +- lib/gitlab/git_access_status.rb | 4 - lib/gitlab/git_access_wiki.rb | 2 +- spec/lib/gitlab/checks/change_access_spec.rb | 57 +++----- spec/lib/gitlab/git_access_spec.rb | 187 +++++++++++++----------- spec/lib/gitlab/git_access_wiki_spec.rb | 7 +- 9 files changed, 182 insertions(+), 203 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 073c76933c1..b6b62da7b60 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,36 +1,27 @@ class Projects::GitHttpController < Projects::GitHttpClientController include WorkhorseRequest + before_action :access_check + + rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403 + rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404 + # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) def info_refs - if upload_pack? && upload_pack_allowed? - log_user_activity + log_user_activity if upload_pack? - render_ok - elsif receive_pack? && receive_pack_allowed? - render_ok - else - render_denied - end + render_ok end # POST /foo/bar.git/git-upload-pack (git pull) def git_upload_pack - if upload_pack? && upload_pack_allowed? - render_ok - else - render_denied - end + render_ok end # POST /foo/bar.git/git-receive-pack" (git push) def git_receive_pack - if receive_pack? && receive_pack_allowed? - render_ok - else - render_denied - end + render_ok end private @@ -43,10 +34,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController git_command == 'git-upload-pack' end - def receive_pack? - git_command == 'git-receive-pack' - end - def git_command if action_name == 'info_refs' params[:service] @@ -60,16 +47,12 @@ class Projects::GitHttpController < Projects::GitHttpClientController render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) end - def render_denied - if access_check.message == Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found] - render plain: access_check.message, status: :not_found - else - render plain: access_check.message, status: :forbidden - end + def render_403(exception) + render plain: exception.message, status: :forbidden end - def upload_pack_allowed? - access_check.allowed? + def render_404(exception) + render plain: exception.message, status: :not_found end def access @@ -84,11 +67,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController def access_check # Use the magic string '_any' to indicate we do not know what the # changes are. This is also what gitlab-shell does. - @access_check ||= access.check(git_command, '_any') - end - - def receive_pack_allowed? - access_check.allowed? + access.check(git_command, '_any') end def access_klass diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 9ebd4841296..3d60d1da114 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -32,29 +32,31 @@ module API actor.update_last_used_at if actor.is_a?(Key) - access_checker = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess - access_status = access_checker + access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess + access_checker = access_checker_klass .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) - .check(params[:action], params[:changes]) - response = { status: access_status.status, message: access_status.message } + begin + access_status = access_checker.check(params[:action], params[:changes]) + response = { status: access_status.status, message: access_status.message } + rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e + return { status: false, message: e.message } + end - if access_status.status - log_user_activity(actor) + log_user_activity(actor) - # Project id to pass between components that don't share/don't have - # access to the same filesystem mounts - response[:gl_repository] = Gitlab::GlRepository.gl_repository(project, wiki?) + # Project id to pass between components that don't share/don't have + # access to the same filesystem mounts + response[:gl_repository] = Gitlab::GlRepository.gl_repository(project, wiki?) - # Return the repository full path so that gitlab-shell has it when - # handling ssh commands - response[:repository_path] = - if wiki? - project.wiki.repository.path_to_repo - else - project.repository.path_to_repo - end - end + # Return the repository full path so that gitlab-shell has it when + # handling ssh commands + response[:repository_path] = + if wiki? + project.wiki.repository.path_to_repo + else + project.repository.path_to_repo + end response end diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 4b6f8abd61d..e9782623be5 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -33,20 +33,18 @@ module Gitlab def exec return GitAccessStatus.new(true) if skip_authorization - error = push_checks || branch_checks || tag_checks + push_checks + branch_checks + tag_checks - if error - GitAccessStatus.new(false, error) - else - GitAccessStatus.new(true) - end + GitAccessStatus.new(true) end protected def push_checks if user_access.cannot_do_action?(:push_code) - ERROR_MESSAGES[:push_code] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code] end end @@ -54,7 +52,7 @@ module Gitlab return unless @branch_name if deletion? && @branch_name == project.default_branch - return ERROR_MESSAGES[:delete_default_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch] end protected_branch_checks @@ -64,7 +62,7 @@ module Gitlab return unless ProtectedBranch.protected?(project, @branch_name) if forced_push? - return ERROR_MESSAGES[:force_push_protected_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch] end if deletion? @@ -76,22 +74,22 @@ module Gitlab def protected_branch_deletion_checks unless user_access.can_delete_branch?(@branch_name) - return ERROR_MESSAGES[:non_master_delete_protected_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch] end unless protocol == 'web' - ERROR_MESSAGES[:non_web_delete_protected_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch] end end def protected_branch_push_checks if matching_merge_request? unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name) - ERROR_MESSAGES[:merge_protected_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch] end else unless user_access.can_push_to_branch?(@branch_name) - ERROR_MESSAGES[:push_protected_branch] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch] end end end @@ -100,7 +98,7 @@ module Gitlab return unless @tag_name if tag_exists? && user_access.cannot_do_action?(:admin_project) - return ERROR_MESSAGES[:change_existing_tags] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags] end protected_tag_checks @@ -109,11 +107,11 @@ module Gitlab def protected_tag_checks return unless ProtectedTag.protected?(project, @tag_name) - return ERROR_MESSAGES[:update_protected_tag] if update? - return ERROR_MESSAGES[:delete_protected_tag] if deletion? + raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update? + raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion? unless user_access.can_create_tag?(@tag_name) - return ERROR_MESSAGES[:create_protected_tag] + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag] end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 1ffac5385c2..f43359d7dbd 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -3,6 +3,7 @@ module Gitlab class GitAccess UnauthorizedError = Class.new(StandardError) + NotFoundError = Class.new(StandardError) ERROR_MESSAGES = { upload: 'You are not allowed to upload code for this project.', @@ -47,8 +48,6 @@ module Gitlab end build_status_object(true) - rescue UnauthorizedError => ex - build_status_object(false, ex.message) end def guest_can_download_code? @@ -90,7 +89,7 @@ module Gitlab def check_project_accessibility! if project.blank? || !can_read_project? - raise UnauthorizedError, ERROR_MESSAGES[:project_not_found] + raise NotFoundError, ERROR_MESSAGES[:project_not_found] end end @@ -234,8 +233,8 @@ module Gitlab end end - def build_status_object(status, message = '') - Gitlab::GitAccessStatus.new(status, message) + def build_status_object(status) + Gitlab::GitAccessStatus.new(status) end end end diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb index 09bb01be694..94edf80c0f6 100644 --- a/lib/gitlab/git_access_status.rb +++ b/lib/gitlab/git_access_status.rb @@ -7,9 +7,5 @@ module Gitlab @status = status @message = message end - - def to_json(opts = nil) - { status: @status, message: @message }.to_json(opts) - end end end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index dccafd2cae8..4c87482430f 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -16,7 +16,7 @@ module Gitlab if user_access.can_do_action?(:create_wiki) build_status_object(true) else - build_status_object(false, ERROR_MESSAGES[:write_to_wiki]) + raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki] end end end diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 8d81ed5856e..c0c309d8179 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -23,29 +23,27 @@ describe Gitlab::Checks::ChangeAccess, lib: true do before { project.add_developer(user) } context 'without failed checks' do - it "doesn't return any error" do - expect(subject.status).to be(true) + it "doesn't raise an error" do + expect { subject }.not_to raise_error end end context 'when the user is not allowed to push code' do - it 'returns an error' do + it 'raises an error' do expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to push code to this project.') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') end end context 'tags check' do let(:ref) { 'refs/tags/v1.0.0' } - it 'returns an error if the user is not allowed to update tags' do + it 'raises an error if the user is not allowed to update tags' do allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false) - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to change existing tags on this project.') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') end context 'with protected tag' do @@ -59,8 +57,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:newrev) { '0000000000000000000000000000000000000000' } it 'is prevented' do - expect(subject.status).to be(false) - expect(subject.message).to include('cannot be deleted') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/) end end @@ -69,8 +66,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } it 'is prevented' do - expect(subject.status).to be(false) - expect(subject.message).to include('cannot be updated') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/) end end end @@ -81,15 +77,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:ref) { 'refs/tags/v9.1.0' } it 'prevents creation below access level' do - expect(subject.status).to be(false) - expect(subject.message).to include('allowed to create this tag as it is protected') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/) end context 'when user has access' do let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') } it 'allows tag creation' do - expect(subject.status).to be(true) + expect { subject }.not_to raise_error end end end @@ -101,9 +96,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:newrev) { '0000000000000000000000000000000000000000' } let(:ref) { 'refs/heads/master' } - it 'returns an error' do - expect(subject.status).to be(false) - expect(subject.message).to eq('The default branch of a project cannot be deleted.') + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.') end end @@ -113,27 +107,24 @@ describe Gitlab::Checks::ChangeAccess, lib: true do allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true) end - it 'returns an error if the user is not allowed to do forced pushes to protected branches' do + it 'raises an error if the user is not allowed to do forced pushes to protected branches' do expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.') end - it 'returns an error if the user is not allowed to merge to protected branches' do + it 'raises an error if the user is not allowed to merge to protected branches' do expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true) expect(user_access).to receive(:can_merge_to_branch?).and_return(false) expect(user_access).to receive(:can_push_to_branch?).and_return(false) - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.') end - it 'returns an error if the user is not allowed to push to protected branches' do + it 'raises an error if the user is not allowed to push to protected branches' do expect(user_access).to receive(:can_push_to_branch?).and_return(false) - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.') + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') end context 'branch deletion' do @@ -141,9 +132,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:ref) { 'refs/heads/feature' } context 'if the user is not allowed to delete protected branches' do - it 'returns an error' do - expect(subject.status).to be(false) - expect(subject.message).to eq('You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.') + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.') end end @@ -156,14 +146,13 @@ describe Gitlab::Checks::ChangeAccess, lib: true do let(:protocol) { 'web' } it 'allows branch deletion' do - expect(subject.status).to be(true) + expect { subject }.not_to raise_error end end context 'over SSH or HTTP' do - it 'returns an error' do - expect(subject.status).to be(false) - expect(subject.message).to eq('You can only delete protected branches using the web interface.') + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.') end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 0efe15856fc..10a7222c2b6 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -18,8 +18,7 @@ describe Gitlab::GitAccess, lib: true do describe '#check with single protocols allowed' do def disable_protocol(protocol) - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, protocol) + allow(Gitlab::ProtocolAccess).to receive(:allowed?).with(protocol).and_return(false) end context 'ssh disabled' do @@ -28,11 +27,11 @@ describe Gitlab::GitAccess, lib: true do end it 'blocks ssh git push' do - expect(access.check('git-receive-pack', '_any').allowed?).to be_falsey + expect { push_access_check }.to raise_unauthorized('Git access over SSH is not allowed') end it 'blocks ssh git pull' do - expect(access.check('git-upload-pack', '_any').allowed?).to be_falsey + expect { pull_access_check }.to raise_unauthorized('Git access over SSH is not allowed') end end @@ -44,11 +43,11 @@ describe Gitlab::GitAccess, lib: true do end it 'blocks http push' do - expect(access.check('git-receive-pack', '_any').allowed?).to be_falsey + expect { push_access_check }.to raise_unauthorized('Git access over HTTP is not allowed') end it 'blocks http git pull' do - expect(access.check('git-upload-pack', '_any').allowed?).to be_falsey + expect { pull_access_check }.to raise_unauthorized('Git access over HTTP is not allowed') end end end @@ -64,23 +63,21 @@ describe Gitlab::GitAccess, lib: true do before { deploy_key.projects << project } it 'allows pull access' do - expect(pull_access_check.allowed?).to be_truthy + expect { pull_access_check }.not_to raise_error end it 'allows push access' do - expect(push_access_check.allowed?).to be_truthy + expect { push_access_check }.not_to raise_error end end context 'when the Deploykey does not have access to the project' do it 'blocks pulls with "not found"' do - expect(pull_access_check.allowed?).to be_falsey - expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') end it 'blocks pushes with "not found"' do - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('The project you were looking for could not be found.') + expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') end end end @@ -90,23 +87,21 @@ describe Gitlab::GitAccess, lib: true do before { project.team << [user, :master] } it 'allows pull access' do - expect(pull_access_check.allowed?).to be_truthy + expect { pull_access_check }.not_to raise_error end it 'allows push access' do - expect(push_access_check.allowed?).to be_truthy + expect { push_access_check }.not_to raise_error end end context 'when the User cannot read the project' do it 'blocks pulls with "not found"' do - expect(pull_access_check.allowed?).to be_falsey - expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') end it 'blocks pushes with "not found"' do - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('The project you were looking for could not be found.') + expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') end end end @@ -117,12 +112,11 @@ describe Gitlab::GitAccess, lib: true do let(:authentication_abilities) { build_authentication_abilities } it 'allows pull access' do - expect(pull_access_check.allowed?).to be_truthy + expect { pull_access_check }.not_to raise_error end it 'does not block pushes with "not found"' do - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('You are not allowed to upload code for this project.') + expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') end end end @@ -134,24 +128,21 @@ describe Gitlab::GitAccess, lib: true do let(:project) { create(:project, :repository, :public) } it 'allows pull access' do - expect(pull_access_check.allowed?).to be_truthy + expect { pull_access_check }.not_to raise_error end it 'does not block pushes with "not found"' do - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('You are not allowed to upload code for this project.') + expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') end end context 'when guests cannot read the project' do it 'blocks pulls with "not found"' do - expect(pull_access_check.allowed?).to be_falsey - expect(pull_access_check.message).to eq('The project you were looking for could not be found.') + expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') end it 'blocks pushes with "not found"' do - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('The project you were looking for could not be found.') + expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') end end end @@ -161,10 +152,8 @@ describe Gitlab::GitAccess, lib: true do let(:project) { nil } it 'blocks any command with "not found"' do - expect(pull_access_check.allowed?).to be_falsey - expect(pull_access_check.message).to eq('The project you were looking for could not be found.') - expect(push_access_check.allowed?).to be_falsey - expect(push_access_check.message).to eq('The project you were looking for could not be found.') + expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') + expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') end end end @@ -181,13 +170,11 @@ describe Gitlab::GitAccess, lib: true do end context 'when calling git-upload-pack' do - subject { access.check('git-upload-pack', '_any') } - it { expect(subject.allowed?).to be_falsey } - it { expect(subject.message).to eq('The command "git-upload-pack" is not allowed.') } + it { expect { pull_access_check }.to raise_unauthorized('The command "git-upload-pack" is not allowed.') } end context 'when calling git-receive-pack' do - it { expect(access.check('git-receive-pack', '_any').allowed?).to be_truthy } + it { expect { push_access_check }.not_to raise_error } end end @@ -197,26 +184,22 @@ describe Gitlab::GitAccess, lib: true do end context 'when calling git-receive-pack' do - subject { access.check('git-receive-pack', '_any') } - it { expect(subject.allowed?).to be_falsey } - it { expect(subject.message).to eq('The command "git-receive-pack" is not allowed.') } + it { expect { push_access_check }.to raise_unauthorized('The command "git-receive-pack" is not allowed.') } end context 'when calling git-upload-pack' do - it { expect(access.check('git-upload-pack', '_any').allowed?).to be_truthy } + it { expect { pull_access_check }.not_to raise_error } end end end end describe '#check_download_access!' do - subject { access.check('git-upload-pack', '_any') } - describe 'master permissions' do before { project.team << [user, :master] } context 'pull code' do - it { expect(subject.allowed?).to be_truthy } + it { expect { pull_access_check }.not_to raise_error } end end @@ -224,8 +207,7 @@ describe Gitlab::GitAccess, lib: true do before { project.team << [user, :guest] } context 'pull code' do - it { expect(subject.allowed?).to be_falsey } - it { expect(subject.message).to match(/You are not allowed to download code/) } + it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') } end end @@ -236,24 +218,22 @@ describe Gitlab::GitAccess, lib: true do end context 'pull code' do - it { expect(subject.allowed?).to be_falsey } - it { expect(subject.message).to match(/Your account has been blocked/) } + it { expect { pull_access_check }.to raise_unauthorized('Your account has been blocked.') } end end describe 'without access to project' do context 'pull code' do - it { expect(subject.allowed?).to be_falsey } + it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') } end context 'when project is public' do let(:public_project) { create(:project, :public, :repository) } - let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) } - subject { guest_access.check('git-upload-pack', '_any') } + let(:access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) } context 'when repository is enabled' do it 'give access to download code' do - expect(subject.allowed?).to be_truthy + expect { pull_access_check }.not_to raise_error end end @@ -261,8 +241,7 @@ describe Gitlab::GitAccess, lib: true do it 'does not give access to download code' do public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) - expect(subject.allowed?).to be_falsey - expect(subject.message).to match(/You are not allowed to download code/) + expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') end end end @@ -276,26 +255,26 @@ describe Gitlab::GitAccess, lib: true do context 'when project is authorized' do before { key.projects << project } - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end context 'when unauthorized' do context 'from public project' do let(:project) { create(:project, :public, :repository) } - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end context 'from internal project' do let(:project) { create(:project, :internal, :repository) } - it { expect(subject).not_to be_allowed } + it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') } end context 'from private project' do let(:project) { create(:project, :private, :repository) } - it { expect(subject).not_to be_allowed } + it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') } end end end @@ -308,7 +287,7 @@ describe Gitlab::GitAccess, lib: true do let(:project) { create(:project, :repository, namespace: user.namespace) } context 'pull code' do - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end end @@ -316,7 +295,7 @@ describe Gitlab::GitAccess, lib: true do before { project.team << [user, :reporter] } context 'pull code' do - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end end @@ -327,13 +306,13 @@ describe Gitlab::GitAccess, lib: true do before { project.team << [user, :reporter] } context 'pull code' do - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end end context 'when is not member of the project' do context 'pull code' do - it { expect(subject).not_to be_allowed } + it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') } end end end @@ -342,7 +321,7 @@ describe Gitlab::GitAccess, lib: true do let(:actor) { :ci } context 'pull code' do - it { expect(subject).to be_allowed } + it { expect { pull_access_check }.not_to raise_error } end end end @@ -532,42 +511,32 @@ describe Gitlab::GitAccess, lib: true do end end - shared_examples 'pushing code' do |can| - subject { access.check('git-receive-pack', '_any') } + describe 'build authentication abilities' do + let(:authentication_abilities) { build_authentication_abilities } context 'when project is authorized' do - before { authorize } + before { project.team << [user, :reporter] } - it { expect(subject).public_send(can, be_allowed) } + it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') } end context 'when unauthorized' do context 'to public project' do let(:project) { create(:project, :public, :repository) } - it { expect(subject).not_to be_allowed } + it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') } end context 'to internal project' do let(:project) { create(:project, :internal, :repository) } - it { expect(subject).not_to be_allowed } + it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') } end context 'to private project' do let(:project) { create(:project, :private, :repository) } - it { expect(subject).not_to be_allowed } - end - end - end - - describe 'build authentication abilities' do - let(:authentication_abilities) { build_authentication_abilities } - - it_behaves_like 'pushing code', :not_to do - def authorize - project.team << [user, :reporter] + it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') } end end end @@ -579,9 +548,29 @@ describe Gitlab::GitAccess, lib: true do context 'when deploy_key can push' do let(:can_push) { true } - it_behaves_like 'pushing code', :to do - def authorize - key.projects << project + context 'when project is authorized' do + before { key.projects << project } + + it { expect { push_access_check }.not_to raise_error } + end + + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public, :repository) } + + it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') } + end + + context 'to internal project' do + let(:project) { create(:project, :internal, :repository) } + + it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') } + end + + context 'to private project' do + let(:project) { create(:project, :private, :repository) } + + it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') } end end end @@ -589,9 +578,29 @@ describe Gitlab::GitAccess, lib: true do context 'when deploy_key cannot push' do let(:can_push) { false } - it_behaves_like 'pushing code', :not_to do - def authorize - key.projects << project + context 'when project is authorized' do + before { key.projects << project } + + it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') } + end + + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public, :repository) } + + it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') } + end + + context 'to internal project' do + let(:project) { create(:project, :internal, :repository) } + + it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') } + end + + context 'to private project' do + let(:project) { create(:project, :private, :repository) } + + it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') } end end end @@ -599,6 +608,14 @@ describe Gitlab::GitAccess, lib: true do private + def raise_unauthorized(message) + raise_error(Gitlab::GitAccess::UnauthorizedError, message) + end + + def raise_not_found(message) + raise_error(Gitlab::GitAccess::NotFoundError, message) + end + def build_authentication_abilities [ :read_project, diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 1ae293416e4..a1eb95750ba 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::GitAccessWiki, lib: true do subject { access.check('git-receive-pack', changes) } - it { expect(subject.allowed?).to be_truthy } + it { expect { subject }.not_to raise_error } end def changes @@ -36,7 +36,7 @@ describe Gitlab::GitAccessWiki, lib: true do context 'when wiki feature is enabled' do it 'give access to download wiki code' do - expect(subject.allowed?).to be_truthy + expect { subject }.not_to raise_error end end @@ -44,8 +44,7 @@ describe Gitlab::GitAccessWiki, lib: true do it 'does not give access to download wiki code' do project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) - expect(subject.allowed?).to be_falsey - expect(subject.message).to match(/You are not allowed to download code/) + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to download code from this project.') end end end -- cgit v1.2.1 From e8972c11904c31fc614a31483098814adc38c2ac Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 22 May 2017 11:28:51 -0700 Subject: Clarify error messages And refactor to self-document a little better. --- lib/gitlab/git_access.rb | 34 ++++++++++++++++++++++++++-------- spec/lib/gitlab/git_access_spec.rb | 4 ++-- spec/requests/git_http_spec.rb | 6 +++--- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index f43359d7dbd..176b0991971 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -14,8 +14,8 @@ module Gitlab project_not_found: 'The project you were looking for could not be found.', account_blocked: 'Your account has been blocked.', command_not_allowed: "The command you're trying to execute is not allowed.", - upload_pack_disabled_in_config: 'The command "git-upload-pack" is not allowed.', - receive_pack_disabled_in_config: 'The command "git-receive-pack" is not allowed.' + upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.', + receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.' }.freeze DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze @@ -94,12 +94,22 @@ module Gitlab end def check_command_disabled!(cmd) - if http? - if upload_pack?(cmd) && !Gitlab.config.gitlab_shell.upload_pack - raise UnauthorizedError, ERROR_MESSAGES[:upload_pack_disabled_in_config] - elsif receive_pack?(cmd) && !Gitlab.config.gitlab_shell.receive_pack - raise UnauthorizedError, ERROR_MESSAGES[:receive_pack_disabled_in_config] - end + if upload_pack?(cmd) + check_upload_pack_disabled! + elsif receive_pack?(cmd) + check_receive_pack_disabled! + end + end + + def check_upload_pack_disabled! + if http? && upload_pack_disabled_over_http? + raise UnauthorizedError, ERROR_MESSAGES[:upload_pack_disabled_over_http] + end + end + + def check_receive_pack_disabled! + if http? && receive_pack_disabled_over_http? + raise UnauthorizedError, ERROR_MESSAGES[:receive_pack_disabled_over_http] end end @@ -215,6 +225,14 @@ module Gitlab command == 'git-receive-pack' end + def upload_pack_disabled_over_http? + !Gitlab.config.gitlab_shell.upload_pack + end + + def receive_pack_disabled_over_http? + !Gitlab.config.gitlab_shell.receive_pack + end + protected def user diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 10a7222c2b6..36d1d777583 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -170,7 +170,7 @@ describe Gitlab::GitAccess, lib: true do end context 'when calling git-upload-pack' do - it { expect { pull_access_check }.to raise_unauthorized('The command "git-upload-pack" is not allowed.') } + it { expect { pull_access_check }.to raise_unauthorized('Pulling over HTTP is not allowed.') } end context 'when calling git-receive-pack' do @@ -184,7 +184,7 @@ describe Gitlab::GitAccess, lib: true do end context 'when calling git-receive-pack' do - it { expect { push_access_check }.to raise_unauthorized('The command "git-receive-pack" is not allowed.') } + it { expect { push_access_check }.to raise_unauthorized('Pushing over HTTP is not allowed.') } end context 'when calling git-upload-pack' do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index ab7c56fcdf0..f018b48ceb2 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -254,7 +254,7 @@ describe 'Git HTTP requests', lib: true do it 'rejects pushes with 403 Forbidden' do upload(path, env) do |response| expect(response).to have_http_status(:forbidden) - expect(response.body).to eq(git_access_error(:receive_pack_disabled_in_config)) + expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http)) end end end @@ -265,7 +265,7 @@ describe 'Git HTTP requests', lib: true do download(path, env) do |response| expect(response).to have_http_status(:forbidden) - expect(response.body).to eq(git_access_error(:upload_pack_disabled_in_config)) + expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http)) end end end @@ -541,7 +541,7 @@ describe 'Git HTTP requests', lib: true do context 'when the repo does not exist' do let(:project) { create(:empty_project) } - + it 'rejects pulls with 403 Forbidden' do clone_get path, env -- cgit v1.2.1 From 7d469cf1c1f356d950abc58ecbe6aa4ec15bd72b Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 22 May 2017 11:48:25 -0700 Subject: Fix would-be regression https://gitlab.com/gitlab-org/gitlab-ce/commit/57e3e942de1adef2c8621905370f07d7da7870c4 I changed it to a separate condition rather than depending on the order of the case-when statements to prevent this mistake again. --- lib/gitlab/git_access.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 176b0991971..75cc69c02f7 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -243,9 +243,7 @@ module Gitlab when User actor when Key - actor.user - when DeployKey - nil + actor.user unless actor.is_a?(DeployKey) when :ci nil end -- cgit v1.2.1 From 0a0f66c816469e197237a6eeaedeb959b5d1c823 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 22 May 2017 12:44:59 -0700 Subject: Refactor to remove a special case --- lib/gitlab/ci_access.rb | 9 +++++++++ lib/gitlab/git_access.rb | 12 +++++------- spec/lib/gitlab/ci_access_spec.rb | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 lib/gitlab/ci_access.rb create mode 100644 spec/lib/gitlab/ci_access_spec.rb diff --git a/lib/gitlab/ci_access.rb b/lib/gitlab/ci_access.rb new file mode 100644 index 00000000000..def1373d8cf --- /dev/null +++ b/lib/gitlab/ci_access.rb @@ -0,0 +1,9 @@ +module Gitlab + # For backwards compatibility, generic CI (which is a build without a user) is + # allowed to :build_download_code without any other checks. + class CiAccess + def can_do_action?(action) + action == :build_download_code + end + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 75cc69c02f7..f44426d62b8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -29,7 +29,11 @@ module Gitlab @project = project @protocol = protocol @authentication_abilities = authentication_abilities - @user_access = UserAccess.new(user, project: project) + @user_access = if ci? + CiAccess.new + else + UserAccess.new(user, project: project) + end end def check(cmd, changes) @@ -62,11 +66,6 @@ module Gitlab authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) end - # Allow generic CI (build without a user) for backwards compatibility - def ci_can_download_code? - authentication_abilities.include?(:build_download_code) && ci? - end - def protocol_allowed? Gitlab::ProtocolAccess.allowed?(protocol) end @@ -129,7 +128,6 @@ module Gitlab return if deploy_key? passed = user_can_download_code? || - ci_can_download_code? || build_can_download_code? || guest_can_download_code? diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb new file mode 100644 index 00000000000..eaf8f1d0f1c --- /dev/null +++ b/spec/lib/gitlab/ci_access_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Gitlab::CiAccess, lib: true do + let(:access) { Gitlab::CiAccess.new } + + describe '#can_do_action?' do + context 'when action is :build_download_code' do + it { expect(access.can_do_action?(:build_download_code)).to be_truthy } + end + + context 'when action is not :build_download_code' do + it { expect(access.can_do_action?(:download_code)).to be_falsey } + end + end +end -- cgit v1.2.1 From b50a22894d4503cf99cecb8162696f854e1b3f85 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 23 May 2017 11:34:58 -0700 Subject: Refactor construction of response --- lib/api/helpers/internal_helpers.rb | 16 ++++++++++++++++ lib/api/internal.rb | 20 ++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 264df7271a3..d3732d67622 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -42,6 +42,22 @@ module API @project, @wiki = Gitlab::RepoPath.parse(params[:project]) end end + + # Project id to pass between components that don't share/don't have + # access to the same filesystem mounts + def gl_repository + Gitlab::GlRepository.gl_repository(project, wiki?) + end + + # Return the repository full path so that gitlab-shell has it when + # handling ssh commands + def repository_path + if wiki? + project.wiki.repository.path_to_repo + else + project.repository.path_to_repo + end + end end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 3d60d1da114..f2d41754afd 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -37,26 +37,18 @@ module API .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) begin - access_status = access_checker.check(params[:action], params[:changes]) - response = { status: access_status.status, message: access_status.message } + access_checker.check(params[:action], params[:changes]) rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e return { status: false, message: e.message } end log_user_activity(actor) - # Project id to pass between components that don't share/don't have - # access to the same filesystem mounts - response[:gl_repository] = Gitlab::GlRepository.gl_repository(project, wiki?) - - # Return the repository full path so that gitlab-shell has it when - # handling ssh commands - response[:repository_path] = - if wiki? - project.wiki.repository.path_to_repo - else - project.repository.path_to_repo - end + response = { + status: true, + gl_repository: gl_repository, + repository_path: repository_path + } response end -- cgit v1.2.1 From 0e3cfc75a3ae244571385c878d0025bdf7a7d394 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 23 May 2017 12:21:57 -0700 Subject: Remove GitAccessStatus (no longer needed) --- lib/gitlab/checks/change_access.rb | 4 ++-- lib/gitlab/git_access.rb | 14 ++++---------- lib/gitlab/git_access_status.rb | 11 ----------- lib/gitlab/git_access_wiki.rb | 6 +++--- 4 files changed, 9 insertions(+), 26 deletions(-) delete mode 100644 lib/gitlab/git_access_status.rb diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index e9782623be5..b6805230348 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -31,13 +31,13 @@ module Gitlab end def exec - return GitAccessStatus.new(true) if skip_authorization + return true if skip_authorization push_checks branch_checks tag_checks - GitAccessStatus.new(true) + true end protected diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index f44426d62b8..4807dc9a5fb 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -51,7 +51,7 @@ module Gitlab check_push_access!(changes) end - build_status_object(true) + true end def guest_can_download_code? @@ -167,11 +167,9 @@ module Gitlab # Iterate over all changes to find if user allowed all of them to be applied changes_list.each do |change| - status = check_single_change_access(change) - unless status.allowed? - # If user does not have access to make at least one change - cancel all push - raise UnauthorizedError, status.message - end + # If user does not have access to make at least one change, cancel all + # push by allowing the exception to bubble up + check_single_change_access(change) end end @@ -246,9 +244,5 @@ module Gitlab nil end end - - def build_status_object(status) - Gitlab::GitAccessStatus.new(status) - end end end diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb deleted file mode 100644 index 94edf80c0f6..00000000000 --- a/lib/gitlab/git_access_status.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Gitlab - class GitAccessStatus - attr_accessor :status, :message - alias_method :allowed?, :status - - def initialize(status, message = '') - @status = status - @message = message - end - end -end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 4c87482430f..1fe5155c093 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -13,11 +13,11 @@ module Gitlab end def check_single_change_access(change) - if user_access.can_do_action?(:create_wiki) - build_status_object(true) - else + unless user_access.can_do_action?(:create_wiki) raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki] end + + true end end end -- cgit v1.2.1 From d7eee7332b4a3eda49c07dfddc734bed25813f11 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 23 May 2017 12:26:46 -0700 Subject: Extract and memoize `user_access` Because it is sometimes never used. --- lib/gitlab/git_access.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 4807dc9a5fb..0a19d24eb20 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -22,18 +22,13 @@ module Gitlab PUSH_COMMANDS = %w{ git-receive-pack }.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS - attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities + attr_reader :actor, :project, :protocol, :authentication_abilities def initialize(actor, project, protocol, authentication_abilities:) @actor = actor @project = project @protocol = protocol @authentication_abilities = authentication_abilities - @user_access = if ci? - CiAccess.new - else - UserAccess.new(user, project: project) - end end def check(cmd, changes) @@ -244,5 +239,13 @@ module Gitlab nil end end + + def user_access + @user_access ||= if ci? + CiAccess.new + else + UserAccess.new(user, project: project) + end + end end end -- cgit v1.2.1 From ad16e1ba8cd1c56bed2569aa789db430cb96984c Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 5 Jun 2017 08:17:08 -0700 Subject: Add changelog entry --- changelogs/unreleased/mk-fix-git-over-http-rejections.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/mk-fix-git-over-http-rejections.yml diff --git a/changelogs/unreleased/mk-fix-git-over-http-rejections.yml b/changelogs/unreleased/mk-fix-git-over-http-rejections.yml new file mode 100644 index 00000000000..e75740e913f --- /dev/null +++ b/changelogs/unreleased/mk-fix-git-over-http-rejections.yml @@ -0,0 +1,4 @@ +--- +title: Fix Git-over-HTTP error statuses and improve error messages +merge_request: 11398 +author: -- cgit v1.2.1 From f09b7f56077f8b1deb88dd565717e8ed0d8e6aee Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 13:35:37 +0100 Subject: Support hard deletion in Admin::UsersController#destroy --- app/controllers/admin/users_controller.rb | 2 +- doc/user/profile/account/delete_account.md | 3 ++- spec/controllers/admin/users_controller_spec.rb | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 563bcc65bd6..bace99dad58 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController end def destroy - DeleteUserWorker.perform_async(current_user.id, user.id) + user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete)) respond_to do |format| format.html { redirect_to admin_users_path, notice: "The user is being deleted." } diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index 6e274a152e5..e7596f5c577 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -25,7 +25,8 @@ Instead of being deleted, these records will be moved to a system-wide When a user is deleted from an abuse report or spam log, these associated records are not ghosted and will be removed, along with any groups the user is a sole owner of. Administrators can also request this behaviour when -deleting users from the [API](../../../api/users.md#user-deletion) +deleting users from the [API](../../../api/users.md#user-deletion) or the +admin area. [ce-7393]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7393 [ce-10273]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273 diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 2ab2ca1b667..7d6c317482f 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -10,15 +10,26 @@ describe Admin::UsersController do describe 'DELETE #user with projects' do let(:project) { create(:empty_project, namespace: user.namespace) } + let!(:issue) { create(:issue, author: user) } before do project.team << [user, :developer] end - it 'deletes user' do + it 'deletes user and ghosts their contributions' do delete :destroy, id: user.username, format: :json + + expect(response).to have_http_status(200) + expect(User.exists?(user.id)).to be_falsy + expect(issue.reload.author).to be_ghost + end + + it 'deletes the user and their contributions when hard delete is specified' do + delete :destroy, id: user.username, hard_delete: true, format: :json + expect(response).to have_http_status(200) - expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound) + expect(User.exists?(user.id)).to be_falsy + expect(Issue.exists?(issue.id)).to be_falsy end end -- cgit v1.2.1 From 8ce26b3ab64caefce2f7bfba1448428657c0fecc Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 13:36:36 +0100 Subject: Allow users to be hard-removed from the admin users list page --- app/views/admin/users/_user.html.haml | 14 ++++++++++---- spec/features/admin/admin_users_spec.rb | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 46d2e3b3de1..4cf4a57ba18 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -34,9 +34,15 @@ - if user.access_locked? %li = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } - - if user.can_be_removed? && can?(current_user, :destroy_user, user) + - if can?(current_user, :destroy_user, user) %li.divider + - if user.can_be_removed? + %li + = link_to 'Remove user', admin_user_path(user), + data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, + method: :delete %li - = link_to 'Delete user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" }, - class: 'btn btn-remove btn-block', - method: :delete + = link_to 'Remove user and contributions', admin_user_path(user, hard_delete: true), + data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and comments authored by this user, and groups owned solely by them, will also be removed! Are you sure?" }, + class: 'btn btn-remove btn-block', + method: :delete diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 376e80571d0..2d936dbbc80 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -22,7 +22,8 @@ describe "Admin::Users", feature: true do expect(page).to have_content(user.email) expect(page).to have_content(user.name) expect(page).to have_link('Block', href: block_admin_user_path(user)) - expect(page).to have_link('Delete', href: admin_user_path(user)) + expect(page).to have_link('Remove user', href: admin_user_path(user)) + expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) end describe 'Two-factor Authentication filters' do -- cgit v1.2.1 From 11fb83fcc0efa91f60e516d7fb23d035b8f78634 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 14:42:06 +0100 Subject: Allow users to be hard-deleted from the admin user show page --- app/views/admin/users/show.html.haml | 21 ++++++++++++++++++++- spec/features/admin/admin_users_spec.rb | 3 +++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 89d0bbb7126..b556ff056c0 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -177,7 +177,7 @@ %p Deleting a user has the following effects: = render 'users/deletion_guidance', user: @user %br - = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + = link_to 'Remove user', admin_user_path(@user), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" - else - if @user.solo_owned_groups.present? %p @@ -188,3 +188,22 @@ - else %p You don't have access to delete this user. + + .panel.panel-danger + .panel-heading + Remove user and contributions + .panel-body + - if can?(current_user, :destroy_user, @user) + %p + This option deletes the user and any contributions that + would usually be moved to the + = succeed "." do + = link_to "system ghost user", help_page_path("user/profile/account/delete_account") + As well as the user's personal projects, groups owned solely by + the user, and projects in them, will also be removed. Commits + to other projects are unaffected. + %br + = link_to 'Remove user and contributions', admin_user_path(@user, hard_delete: true), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + - else + %p + You don't have access to delete this user. diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 2d936dbbc80..301a47169a4 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -117,6 +117,9 @@ describe "Admin::Users", feature: true do expect(page).to have_content(user.email) expect(page).to have_content(user.name) + expect(page).to have_link('Block user', href: block_admin_user_path(user)) + expect(page).to have_link('Remove user', href: admin_user_path(user)) + expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) end describe 'Impersonation' do -- cgit v1.2.1 From 2cbe716a54753757982a800d8196b0480d699504 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 14:27:08 +0100 Subject: Add changelog entry --- changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml diff --git a/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml b/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml new file mode 100644 index 00000000000..2308a528580 --- /dev/null +++ b/changelogs/unreleased/28694-hard-delete-user-from-admin-panel.yml @@ -0,0 +1,4 @@ +--- +title: Allow users to be hard-deleted from the admin panel +merge_request: 11874 +author: -- cgit v1.2.1 From ef4e913597611ee88cfcac226a409f39873eb676 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 5 Jun 2017 10:39:32 -0700 Subject: Remove unnecessary variable --- lib/api/internal.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index f2d41754afd..38631953014 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -44,13 +44,11 @@ module API log_user_activity(actor) - response = { + { status: true, gl_repository: gl_repository, repository_path: repository_path } - - response end post "/lfs_authenticate" do -- cgit v1.2.1 From b2800ee0c7c0ca47bb11b21b6b32134ae6f4594e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Jun 2017 17:28:54 +0100 Subject: Add a Rake task to aid in rotating otp_key_base --- .../unreleased/29690-rotate-otp-key-base.yml | 4 + doc/raketasks/user_management.md | 79 ++++++++++++++++++++ lib/gitlab/otp_key_rotator.rb | 87 ++++++++++++++++++++++ lib/tasks/gitlab/two_factor.rake | 16 ++++ spec/lib/gitlab/otp_key_rotator_spec.rb | 70 +++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 changelogs/unreleased/29690-rotate-otp-key-base.yml create mode 100644 lib/gitlab/otp_key_rotator.rb create mode 100644 spec/lib/gitlab/otp_key_rotator_spec.rb diff --git a/changelogs/unreleased/29690-rotate-otp-key-base.yml b/changelogs/unreleased/29690-rotate-otp-key-base.yml new file mode 100644 index 00000000000..94d73a24758 --- /dev/null +++ b/changelogs/unreleased/29690-rotate-otp-key-base.yml @@ -0,0 +1,4 @@ +--- +title: Add a Rake task to aid in rotating otp_key_base +merge_request: 11881 +author: diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 044b104f5c2..3ae46019daf 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -71,6 +71,85 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` +## Rotate Two-factor Authentication (2FA) encryption key + +GitLab stores the secret data enabling 2FA to work in an encrypted database +column. The encryption key for this data is known as `otp_key_base`, and is +stored in `config/secrets.yml`. + + +If that file is leaked, but the individual 2FA secrets have not, it's possible +to re-encrypt those secrets with a new encryption key. This allows you to change +the leaked key without forcing all users to change their 2FA details. + +First, look up the old key. This is in the `config/secrets.yml` file, but +**make sure you're working with the production section**. The line you're +interested in will look like this: + +```yaml +production: + otp_key_base: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +``` + +Next, generate a new secret: + +``` +# omnibus-gitlab +sudo gitlab-rake secret + +# installation from source +bundle exec rake secret RAILS_ENV=production +``` + +Now you need to stop the GitLab server, back up the existing secrets file and +update the database: + +``` +# omnibus-gitlab +sudo gitlab-ctl stop +sudo cp config/secrets.yml config/secrets.yml.bak +sudo gitlab-rake gitlab:two_factor:rotate_key:apply filename=backup.csv old_key= new_key= + +# installation from source +sudo /etc/init.d/gitlab stop +cp config/secrets.yml config/secrets.yml.bak +bundle exec rake gitlab:two_factor:rotate_key:apply filename=backup.csv old_key= new_key= RAILS_ENV=production +``` + +The `` value can be read from `config/secrets.yml`; `` was +generated earlier. The **encrypted** values for the user 2FA secrets will be +written to the specified `filename` - you can use this to rollback in case of +error. + +Finally, change `config/secrets.yml` to set `otp_key_base` to `` and +restart. Again, make sure you're operating in the **production** section. + +``` +# omnibus-gitlab +sudo gitlab-ctl start + +# installation from source +sudo /etc/init.d/gitlab start +``` + +If there are any problems (perhaps using the wrong value for `old_key`), you can +restore your backup of `config/secrets.yml` and rollback the changes: + +``` +# omnibus-gitlab +sudo gitlab-ctl stop +sudo gitlab-rake gitlab:two_factor:rotate_key:rollback filename=backup.csv +sudo cp config/secrets.yml.bak config/secrets.yml +sudo gitlab-ctl start + +# installation from source +sudo /etc/init.d/gitlab start +bundle exec rake gitlab:two_factor:rotate_key:rollback filename=backup.csv RAILS_ENV=production +cp config/secrets.yml.bak config/secrets.yml +sudo /etc/init.d/gitlab start + +``` + ## Clear authentication tokens for all users. Important! Data loss! Clear authentication tokens for all users in the GitLab database. This diff --git a/lib/gitlab/otp_key_rotator.rb b/lib/gitlab/otp_key_rotator.rb new file mode 100644 index 00000000000..0d541935bc6 --- /dev/null +++ b/lib/gitlab/otp_key_rotator.rb @@ -0,0 +1,87 @@ +module Gitlab + # The +otp_key_base+ param is used to encrypt the User#otp_secret attribute. + # + # When +otp_key_base+ is changed, it invalidates the current encrypted values + # of User#otp_secret. This class can be used to decrypt all the values with + # the old key, encrypt them with the new key, and and update the database + # with the new values. + # + # For persistence between runs, a CSV file is used with the following columns: + # + # user_id, old_value, new_value + # + # Only the encrypted values are stored in this file. + # + # As users may have their 2FA settings changed at any time, this is only + # guaranteed to be safe if run offline. + class OtpKeyRotator + HEADERS = %w[user_id old_value new_value].freeze + + attr_reader :filename + + # Create a new rotator. +filename+ is used to store values by +calculate!+, + # and to update the database with new and old values in +apply!+ and + # +rollback!+, respectively. + def initialize(filename) + @filename = filename + end + + def rotate!(old_key:, new_key:) + old_key ||= Gitlab::Application.secrets.otp_key_base + + raise ArgumentError.new("Old key is the same as the new key") if old_key == new_key + raise ArgumentError.new("New key is too short! Must be 256 bits") if new_key.size < 64 + + write_csv do |csv| + ActiveRecord::Base.transaction do + User.with_two_factor.in_batches do |relation| + rows = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt) + rows.each do |row| + user = %i[id ciphertext iv salt].zip(row).to_h + new_value = reencrypt(user, old_key, new_key) + + User.where(id: user[:id]).update_all(encrypted_otp_secret: new_value) + csv << [user[:id], user[:ciphertext], new_value] + end + end + end + end + end + + def rollback! + ActiveRecord::Base.transaction do + CSV.foreach(filename, headers: HEADERS, return_headers: false) do |row| + User.where(id: row['user_id']).update_all(encrypted_otp_secret: row['old_value']) + end + end + end + + private + + attr_reader :old_key, :new_key + + def otp_secret_settings + @otp_secret_settings ||= User.encrypted_attributes[:otp_secret] + end + + def reencrypt(user, old_key, new_key) + original = user[:ciphertext].unpack("m").join + opts = { + iv: user[:iv].unpack("m").join, + salt: user[:salt].unpack("m").join, + algorithm: otp_secret_settings[:algorithm], + insecure_mode: otp_secret_settings[:insecure_mode] + } + + decrypted = Encryptor.decrypt(original, opts.merge(key: old_key)) + encrypted = Encryptor.encrypt(decrypted, opts.merge(key: new_key)) + [encrypted].pack("m") + end + + def write_csv(&blk) + File.open(filename, "w") do |file| + yield CSV.new(file, headers: HEADERS, write_headers: false) + end + end + end +end diff --git a/lib/tasks/gitlab/two_factor.rake b/lib/tasks/gitlab/two_factor.rake index fc0ccc726ed..7728c485e8d 100644 --- a/lib/tasks/gitlab/two_factor.rake +++ b/lib/tasks/gitlab/two_factor.rake @@ -19,5 +19,21 @@ namespace :gitlab do puts "There are currently no users with 2FA enabled.".color(:yellow) end end + + namespace :rotate_key do + def rotator + @rotator ||= Gitlab::OtpKeyRotator.new(ENV['filename']) + end + + desc "Encrypt user OTP secrets with a new encryption key" + task apply: :environment do |t, args| + rotator.rotate!(old_key: ENV['old_key'], new_key: ENV['new_key']) + end + + desc "Rollback to secrets encrypted with the old encryption key" + task rollback: :environment do + rotator.rollback! + end + end end end diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb new file mode 100644 index 00000000000..6e6e9ce29ac --- /dev/null +++ b/spec/lib/gitlab/otp_key_rotator_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe Gitlab::OtpKeyRotator do + let(:file) { Tempfile.new("otp-key-rotator-test") } + let(:filename) { file.path } + let(:old_key) { Gitlab::Application.secrets.otp_key_base } + let(:new_key) { "00" * 32 } + let!(:users) { create_list(:user, 5, :two_factor) } + + after do + file.close + file.unlink + end + + def data + CSV.read(filename) + end + + def build_row(user, applied = false) + [user.id.to_s, encrypt_otp(user, old_key), encrypt_otp(user, new_key)] + end + + def encrypt_otp(user, key) + opts = { + value: user.otp_secret, + iv: user.encrypted_otp_secret_iv.unpack("m").join, + salt: user.encrypted_otp_secret_salt.unpack("m").join, + algorithm: 'aes-256-cbc', + insecure_mode: true, + key: key + } + [Encryptor.encrypt(opts)].pack("m") + end + + subject(:rotator) { described_class.new(filename) } + + describe '#rotate!' do + subject(:rotation) { rotator.rotate!(old_key: old_key, new_key: new_key) } + + it 'stores the calculated values in a spreadsheet' do + rotation + + expect(data).to match_array(users.map {|u| build_row(u) }) + end + + context 'new key is too short' do + let(:new_key) { "00" * 31 } + + it { expect { rotation }.to raise_error(ArgumentError) } + end + + context 'new key is the same as the old key' do + let(:new_key) { old_key } + + it { expect { rotation }.to raise_error(ArgumentError) } + end + end + + describe '#rollback!' do + it 'updates rows to the old value' do + file.puts("#{users[0].id},old,new") + file.close + + rotator.rollback! + + expect(users[0].reload.encrypted_otp_secret).to eq('old') + expect(users[1].reload.encrypted_otp_secret).not_to eq('old') + end + end +end -- cgit v1.2.1 From 67d3d0a488bf7db6bab7b07495f227984e9b6bc4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Jun 2017 16:25:59 -0500 Subject: Fix header component spec --- spec/javascripts/pipelines/header_component_spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/javascripts/pipelines/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js index b8531350e43..cecc7ceb53d 100644 --- a/spec/javascripts/pipelines/header_component_spec.js +++ b/spec/javascripts/pipelines/header_component_spec.js @@ -10,6 +10,9 @@ describe('Pipeline details header', () => { beforeEach(() => { HeaderComponent = Vue.extend(headerComponent); + const threeWeeksAgo = new Date(); + threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); + props = { pipeline: { details: { @@ -22,7 +25,7 @@ describe('Pipeline details header', () => { }, }, id: 123, - created_at: '2017-05-08T14:57:39.781Z', + created_at: threeWeeksAgo.toISOString(), user: { web_url: 'path', name: 'Foo', -- cgit v1.2.1 From 810866ecb6c7be4fdac88dc3b2a6cd9ad49ac7bf Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Thu, 1 Jun 2017 15:27:35 +0100 Subject: backports changed import logic from pull mirroring feature into CE --- app/assets/javascripts/importer_status.js | 2 ++ app/controllers/projects/imports_controller.rb | 9 +----- app/models/application_setting.rb | 6 +--- app/models/project.rb | 29 +++++++++++++++++--- app/services/projects/create_service.rb | 5 ++-- app/workers/repository_fork_worker.rb | 38 ++++++++++++++------------ app/workers/repository_import_worker.rb | 24 ++++++++++++---- lib/tasks/import.rake | 2 +- spec/factories/forked_project_links.rb | 4 +++ spec/factories/projects.rb | 16 +++++++++++ spec/features/merge_requests/diffs_spec.rb | 6 ++++ spec/models/project_spec.rb | 16 ++++------- spec/services/projects/create_service_spec.rb | 8 ++---- spec/workers/repository_fork_worker_spec.rb | 26 +++++++++++++----- spec/workers/repository_import_worker_spec.rb | 23 ++++++++++++---- 15 files changed, 143 insertions(+), 71 deletions(-) diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 34e4a257ff9..5b4ca94ed30 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -56,6 +56,8 @@ if (job.import_status === 'finished') { job_item.removeClass("active").addClass("success"); return status_field.html(' done'); + } else if (job.import_status === 'scheduled') { + return status_field.html(" scheduled"); } else if (job.import_status === 'started') { return status_field.html(" started"); } else { diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index a1b84afcd91..4b143434ea5 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -14,14 +14,7 @@ class Projects::ImportsController < Projects::ApplicationController @project.import_url = params[:project][:import_url] if @project.save - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - @project.add_import_job - end + @project.reload.import_schedule end redirect_to namespace_project_import_path(@project.namespace, @project) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 3d12f3c306b..e72e581e580 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -199,7 +199,7 @@ class ApplicationSetting < ActiveRecord::Base ApplicationSetting.define_attribute_methods end - def self.defaults_ce + def self.defaults { after_sign_up_text: nil, akismet_enabled: false, @@ -250,10 +250,6 @@ class ApplicationSetting < ActiveRecord::Base } end - def self.defaults - defaults_ce - end - def self.create_from_defaults create(defaults) end diff --git a/app/models/project.rb b/app/models/project.rb index 7cb79e3249d..92229a3d9e0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -165,7 +165,7 @@ class Project < ActiveRecord::Base has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy, as: :source - has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" + has_one :import_data, dependent: :delete, class_name: "ProjectImportData" has_one :project_feature, dependent: :destroy has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete has_many :container_repositories, dependent: :destroy @@ -298,8 +298,16 @@ class Project < ActiveRecord::Base scope :excluding_project, ->(project) { where.not(id: project) } state_machine :import_status, initial: :none do + event :import_schedule do + transition [:none, :finished, :failed] => :scheduled + end + + event :force_import_start do + transition [:none, :finished, :failed] => :started + end + event :import_start do - transition [:none, :finished] => :started + transition scheduled: :started end event :import_finish do @@ -307,18 +315,23 @@ class Project < ActiveRecord::Base end event :import_fail do - transition started: :failed + transition [:scheduled, :started] => :failed end event :import_retry do transition failed: :started end + state :scheduled state :started state :finished state :failed - after_transition any => :finished, do: :reset_cache_and_import_attrs + after_transition [:none, :finished, :failed] => :scheduled do |project, _| + project.run_after_commit { add_import_job } + end + + after_transition started: :finished, do: :reset_cache_and_import_attrs end class << self @@ -530,9 +543,17 @@ class Project < ActiveRecord::Base end def import_in_progress? + import_started? || import_scheduled? + end + + def import_started? import? && import_status == 'started' end + def import_scheduled? + import_status == 'scheduled' + end + def import_failed? import_status == 'failed' end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 535d93385e6..e874a2d8789 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -48,15 +48,14 @@ module Projects save_project_and_import_data(import_data) - @project.import_start if @project.import? - after_create_actions if @project.persisted? if @project.errors.empty? - @project.add_import_job if @project.import? + @project.import_schedule if @project.import? else fail(error: @project.errors.full_messages.join(', ')) end + @project rescue ActiveRecord::RecordInvalid => e message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} " diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index efc99ec962a..a338523dc6b 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -1,4 +1,6 @@ class RepositoryForkWorker + ForkError = Class.new(StandardError) + include Sidekiq::Worker include Gitlab::ShellAdapter include DedicatedSidekiqQueue @@ -8,29 +10,31 @@ class RepositoryForkWorker source_path: source_path, target_path: target_path) - project = Project.find_by_id(project_id) - - unless project.present? - logger.error("Project #{project_id} no longer exists!") - return - end + project = Project.find(project_id) + project.import_start result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path, project.repository_storage_path, target_path) - unless result - logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") - project.mark_import_as_failed('The project could not be forked.') - return - end + raise ForkError, "Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}" unless result project.repository.after_import - - unless project.valid_repo? - logger.error("Project #{project_id} had an invalid repository after fork") - project.mark_import_as_failed('The forked repository is invalid.') - return - end + raise ForkError, "Project #{project_id} had an invalid repository after fork" unless project.valid_repo? project.import_finish + rescue ForkError => ex + fail_fork(project, ex.message) + raise + rescue => ex + return unless project + + fail_fork(project, ex.message) + raise ForkError, "#{ex.class} #{ex.message}" + end + + private + + def fail_fork(project, message) + Rails.logger.error(message) + project.mark_import_as_failed(message) end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index b33ba2ed7c1..625476b7e01 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -1,4 +1,6 @@ class RepositoryImportWorker + ImportError = Class.new(StandardError) + include Sidekiq::Worker include DedicatedSidekiqQueue @@ -10,6 +12,8 @@ class RepositoryImportWorker @project = Project.find(project_id) @current_user = @project.creator + project.import_start + Gitlab::Metrics.add_event(:import_repository, import_url: @project.import_url, path: @project.path_with_namespace) @@ -17,13 +21,23 @@ class RepositoryImportWorker project.update_columns(import_jid: self.jid, import_error: nil) result = Projects::ImportService.new(project, current_user).execute - - if result[:status] == :error - project.mark_import_as_failed(result[:message]) - return - end + raise ImportError, result[:message] if result[:status] == :error project.repository.after_import project.import_finish + rescue ImportError => ex + fail_import(project, ex.message) + raise + rescue => ex + return unless project + + fail_import(project, ex.message) + raise ImportError, "#{ex.class} #{ex.message}" + end + + private + + def fail_import(project, message) + project.mark_import_as_failed(message) end end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index bc76d7edc55..50b8e331469 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -37,7 +37,7 @@ class GithubImport end def import! - @project.import_start + @project.force_import_start timings = Benchmark.measure do Github::Import.new(@project, @options).execute diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index b16c1272e68..66b0f248959 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -7,5 +7,9 @@ FactoryGirl.define do link.forked_from_project.reload link.forked_to_project.reload end + + trait :forked_to_empty_project do + association :forked_to_project, factory: :empty_project + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index e8a9b688319..5a3e1754ddd 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -24,6 +24,22 @@ FactoryGirl.define do visibility_level Gitlab::VisibilityLevel::PRIVATE end + trait :import_scheduled do + import_status :scheduled + end + + trait :import_started do + import_status :started + end + + trait :import_finished do + import_status :finished + end + + trait :import_failed do + import_status :failed + end + trait :archived do archived true end diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index 4860a2a7498..44013df3ea0 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -68,9 +68,14 @@ feature 'Diffs URL', js: true, feature: true do let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) } let(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") } + before do + forked_project.repository.after_import + end + context 'as author' do it 'shows direct edit link' do login_as(author_user) + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax @@ -81,6 +86,7 @@ feature 'Diffs URL', js: true, feature: true do context 'as user who needs to fork' do it 'shows fork/cancel confirmation' do login_as(user) + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index da1b29a2bda..947cb36dcd8 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -50,7 +50,7 @@ describe Project, models: true do it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } it { is_expected.to have_one(:project_feature).dependent(:destroy) } it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) } - it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) } + it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) } it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_many(:commit_statuses) } @@ -1446,16 +1446,13 @@ describe Project, models: true do end describe 'Project import job' do - let(:project) { create(:empty_project) } - let(:mirror) { false } + let(:project) { create(:empty_project, import_url: generate(:url)) } before do allow_any_instance_of(Gitlab::Shell).to receive(:import_repository) .with(project.repository_storage_path, project.path_with_namespace, project.import_url) .and_return(true) - allow(project).to receive(:repository_exists?).and_return(true) - expect_any_instance_of(Repository).to receive(:after_import) .and_call_original end @@ -1463,8 +1460,7 @@ describe Project, models: true do it 'imports a project' do expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original - project.import_start - project.add_import_job + project.import_schedule expect(project.reload.import_status).to eq('finished') end @@ -1551,7 +1547,7 @@ describe Project, models: true do describe '#add_import_job' do context 'forked' do - let(:forked_project_link) { create(:forked_project_link) } + let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) } let(:forked_from_project) { forked_project_link.forked_from_project } let(:project) { forked_project_link.forked_to_project } @@ -1565,9 +1561,9 @@ describe Project, models: true do end context 'not forked' do - let(:project) { create(:empty_project) } - it 'schedules a RepositoryImportWorker job' do + project = create(:empty_project, import_url: generate(:url)) + expect(RepositoryImportWorker).to receive(:perform_async).with(project.id) project.add_import_job diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 033e6ecd18c..3c566c04d6b 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -161,15 +161,13 @@ describe Projects::CreateService, '#execute', services: true do end context 'when a bad service template is created' do - before do - create(:service, type: 'DroneCiService', project: nil, template: true, active: true) - end - it 'reports an error in the imported project' do opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce' + create(:service, type: 'DroneCiService', project: nil, template: true, active: true) + project = create_project(user, opts) - expect(project.errors.full_messages_for(:base).first).to match /Unable to save project. Error: Unable to save DroneCiService/ + expect(project.errors.full_messages_for(:base).first).to match(/Unable to save project. Error: Unable to save DroneCiService/) expect(project.services.count).to eq 0 end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 5e1cb74c7fc..6ea5569b438 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryForkWorker do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :import_scheduled) } let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:shell) { Gitlab::Shell.new } @@ -46,15 +46,27 @@ describe RepositoryForkWorker do end it "handles bad fork" do + source_path = project.full_path + target_path = fork_project.namespace.full_path + error_message = "Unable to fork project #{project.id} for repository #{source_path} -> #{target_path}" + expect(shell).to receive(:fork_repository).and_return(false) - expect(subject.logger).to receive(:error) + expect do + subject.perform(project.id, '/test/path', source_path, target_path) + end.to raise_error(RepositoryForkWorker::ForkError, error_message) + end - subject.perform( - project.id, - '/test/path', - project.full_path, - fork_project.namespace.full_path) + it 'handles unexpected error' do + source_path = project.full_path + target_path = fork_project.namespace.full_path + + allow_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_raise(RuntimeError) + + expect do + subject.perform(project.id, '/test/path', source_path, target_path) + end.to raise_error(RepositoryForkWorker::ForkError) + expect(project.reload.import_status).to eq('failed') end end end diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 5a2c0671dac..9c277c501f1 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryImportWorker do - let(:project) { create(:empty_project) } + let(:project) { create(:empty_project, :import_scheduled) } subject { described_class.new } @@ -21,15 +21,26 @@ describe RepositoryImportWorker do context 'when the import has failed' do it 'hide the credentials that were used in the import URL' do error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found } - expect_any_instance_of(Projects::ImportService).to receive(:execute). - and_return({ status: :error, message: error }) - allow(subject).to receive(:jid).and_return('123') - subject.perform(project.id) + expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error }) + allow(subject).to receive(:jid).and_return('123') - expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/") + expect do + subject.perform(project.id) + end.to raise_error(RepositoryImportWorker::ImportError, error) expect(project.reload.import_jid).not_to be_nil end end + + context 'with unexpected error' do + it 'marks import as failed' do + allow_any_instance_of(Projects::ImportService).to receive(:execute).and_raise(RuntimeError) + + expect do + subject.perform(project.id) + end.to raise_error(RepositoryImportWorker::ImportError) + expect(project.reload.import_status).to eq('failed') + end + end end end -- cgit v1.2.1 From 0f0af7af335d5ed10c424df7934ff6a397d05989 Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Tue, 6 Jun 2017 02:38:34 +0000 Subject: change headings to improve SEO --- doc/user/project/issues/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index 9598cb801be..fe87e6f9495 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -1,4 +1,4 @@ -# GitLab Issues Documentation +# Issues documentation The GitLab Issue Tracker is an advanced and complete tool for tracking the evolution of a new idea or the process @@ -41,13 +41,13 @@ The image bellow illustrates how an issue looks like: Learn more about it on the [GitLab Issues Functionalities documentation](issues_functionalities.md). -## New Issue +## New issue Read through the [documentation on creating issues](create_new_issue.md). ## Closing issues -Read through the distinct ways to [close issues](closing_issues.md) on GitLab. +Learn distinct ways to [close issues](closing_issues.md) in GitLab. ## Create a merge request from an issue @@ -84,7 +84,7 @@ Learn more about them on the [issue templates documentation](../../project/descr Learn more about [crosslinking](crosslinking_issues.md) issues and merge requests. -### GitLab Issue Board +### Issue Board The [GitLab Issue Board](https://about.gitlab.com/features/issueboard/) is a way to enhance your workflow by organizing and prioritizing issues in GitLab. -- cgit v1.2.1 From e245d7eebe747378f4158b30634ab0da4df59117 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 6 Jun 2017 08:28:39 +0000 Subject: Resolve "When changing project visibility setting, change other dropdowns automatically" --- app/assets/javascripts/project_new.js | 63 ++++++++++++++++++++-- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/projects.scss | 14 +++++ app/helpers/projects_helper.rb | 10 ++-- app/helpers/visibility_level_helper.rb | 4 +- app/views/projects/edit.html.haml | 6 +-- ...etting-change-other-dropdowns-automatically.yml | 4 ++ .../projects/settings/visibility_settings_spec.rb | 4 +- spec/helpers/projects_helper_spec.rb | 4 +- spec/helpers/visibility_level_helper_spec.rb | 2 +- 10 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 04b381fe0e0..c0f757269cb 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,11 +1,17 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */ +function highlightChanges($elm) { + $elm.addClass('highlight-changes'); + setTimeout(() => $elm.removeClass('highlight-changes'), 10); +} + (function() { this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = this.toggleSettings.bind(this); this.$selects = $('.features select'); this.$repoSelects = this.$selects.filter('.js-repo-select'); + this.$projectSelects = this.$selects.not('.js-repo-select'); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { @@ -26,6 +32,42 @@ if (!visibilityContainer) return; const visibilitySelect = new gl.VisibilitySelect(visibilityContainer); visibilitySelect.init(); + + const $visibilitySelect = $(visibilityContainer).find('select'); + let projectVisibility = $visibilitySelect.val(); + const PROJECT_VISIBILITY_PRIVATE = '0'; + + $visibilitySelect.on('change', () => { + const newProjectVisibility = $visibilitySelect.val(); + + if (projectVisibility !== newProjectVisibility) { + this.$projectSelects.each((idx, select) => { + const $select = $(select); + const $options = $select.find('option'); + const values = $.map($options, e => e.value); + + // if switched to "private", limit visibility options + if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) { + if ($select.val() !== values[0] && $select.val() !== values[1]) { + $select.val(values[1]).trigger('change'); + highlightChanges($select); + } + $options.slice(2).disable(); + } + + // if switched from "private", increase visibility for non-disabled options + if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) { + $options.enable(); + if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) { + $select.val(values[values.length - 1]).trigger('change'); + highlightChanges($select); + } + } + }); + + projectVisibility = newProjectVisibility; + } + }); }; ProjectNew.prototype.toggleSettings = function() { @@ -56,8 +98,10 @@ ProjectNew.prototype.toggleRepoVisibility = function () { var $repoAccessLevel = $('.js-repo-access-level select'); + var $lfsEnabledOption = $('.js-lfs-enabled select'); var containerRegistry = document.querySelectorAll('.js-container-registry')[0]; var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled'); + var prevSelectedVal = parseInt($repoAccessLevel.val(), 10); this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") .nextAll() @@ -71,29 +115,40 @@ var $this = $(this); var repoSelectVal = parseInt($this.val(), 10); - $this.find('option').show(); + $this.find('option').enable(); - if (selectedVal < repoSelectVal) { - $this.val(selectedVal); + if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) { + $this.val(selectedVal).trigger('change'); + highlightChanges($this); } - $this.find("option[value='" + selectedVal + "']").nextAll().hide(); + $this.find("option[value='" + selectedVal + "']").nextAll().disable(); }); if (selectedVal) { this.$repoSelects.removeClass('disabled'); + if ($lfsEnabledOption.length) { + $lfsEnabledOption.removeClass('disabled'); + highlightChanges($lfsEnabledOption); + } if (containerRegistry) { containerRegistry.style.display = ''; } } else { this.$repoSelects.addClass('disabled'); + if ($lfsEnabledOption.length) { + $lfsEnabledOption.val('false').addClass('disabled'); + highlightChanges($lfsEnabledOption); + } if (containerRegistry) { containerRegistry.style.display = 'none'; containerRegistryCheckbox.checked = false; } } + + prevSelectedVal = selectedVal; }.bind(this)); }; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b3a86b92d93..4114a050d9a 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -187,6 +187,7 @@ $divergence-graph-bar-bg: #ccc; $divergence-graph-separator-bg: #ccc; $general-hover-transition-duration: 100ms; $general-hover-transition-curve: linear; +$highlight-changes-color: rgb(235, 255, 232); /* diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4719bf826dc..a2f781a6a6e 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -29,6 +29,20 @@ & > .form-group { padding-left: 0; } + + select option[disabled] { + display: none; + } + } + + select { + background: transparent; + transition: background 2s ease-out; + + &.highlight-changes { + background: $highlight-changes-color; + transition: none; + } } .help-block { diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7b0584c42a2..f74e61c9481 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -138,11 +138,15 @@ module ProjectsHelper if @project.private? level = @project.project_feature.send(field) - options.delete('Everyone with access') - highest_available_option = options.values.max if level == ProjectFeature::ENABLED + disabled_option = ProjectFeature::ENABLED + highest_available_option = ProjectFeature::PRIVATE if level == disabled_option end - options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) + options = options_for_select( + options, + selected: highest_available_option || @project.project_feature.public_send(field), + disabled: disabled_option + ) content_tag( :select, diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index b4aaf498068..50757b01538 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -31,9 +31,9 @@ module VisibilityLevelHelper when Gitlab::VisibilityLevel::PRIVATE "Project access must be granted explicitly to each user." when Gitlab::VisibilityLevel::INTERNAL - "The project can be cloned by any logged in user." + "The project can be accessed by any logged in user." when Gitlab::VisibilityLevel::PUBLIC - "The project can be cloned without any authentication." + "The project can be accessed without any authentication." end end diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f5549d7f4cd..c3dab68cea5 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -42,7 +42,7 @@ .col-md-9 .label-light = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level - = link_to "(?)", help_page_path("public_access/public_access") + = link_to icon('question-circle'), help_page_path("public_access/public_access") %span.help-block .col-md-3.visibility-select-container = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level) @@ -92,14 +92,14 @@ .form-group = render 'shared/allow_request_access', form: f - if Gitlab.config.lfs.enabled && current_user.admin? - .row + .row.js-lfs-enabled .col-md-9 = f.label :lfs_enabled, 'LFS', class: 'label-light' %span.help-block Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select', data: { field: 'lfs_enabled' } - if Gitlab.config.registry.enabled diff --git a/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml b/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml new file mode 100644 index 00000000000..dbd8a538d51 --- /dev/null +++ b/changelogs/unreleased/24032-when-changing-project-visibility-setting-change-other-dropdowns-automatically.yml @@ -0,0 +1,4 @@ +--- +title: Automatically adjust project settings to match changes in project visibility +merge_request: 11831 +author: diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index cef315ac9cd..fac4506bdf6 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -14,7 +14,7 @@ feature 'Visibility settings', feature: true, js: true do visibility_select_container = find('.js-visibility-select') expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s - expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + expect(visibility_select_container).to have_content 'The project can be accessed without any authentication.' end scenario 'project visibility description updates on change' do @@ -41,7 +41,7 @@ feature 'Visibility settings', feature: true, js: true do expect(visibility_select_container).not_to have_select '.visibility-select' expect(visibility_select_container).to have_content 'Public' - expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + expect(visibility_select_container).to have_content 'The project can be accessed without any authentication.' end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 54c5ba57bdf..a695621b87a 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -257,7 +257,7 @@ describe ProjectsHelper do result = helper.project_feature_access_select(:issues_access_level) expect(result).to include("Disabled") expect(result).to include("Only team members") - expect(result).not_to include("Everyone with access") + expect(result).to have_selector('option[disabled]', text: "Everyone with access") end end @@ -272,7 +272,7 @@ describe ProjectsHelper do expect(result).to include("Disabled") expect(result).to include("Only team members") - expect(result).not_to include("Everyone with access") + expect(result).to have_selector('option[disabled]', text: "Everyone with access") expect(result).to have_selector('option[selected]', text: "Only team members") end end diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index 8942b00b128..ad19cf9263d 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -37,7 +37,7 @@ describe VisibilityLevelHelper do it "describes public projects" do expect(project_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC)) - .to eq "The project can be cloned without any authentication." + .to eq "The project can be accessed without any authentication." end end -- cgit v1.2.1