GitLab 汉化指南(安装/升级)

手工安装与升级

推荐按照 gitlab-ce 源代码中 doc/install/installation.md 的内容手工安装 GitLab 中文版本。

相关修改只需要在 Clone the Source 步骤中使用 https://gitlab.com/larryli/gitlab.git 仓库和当前版本的 7-13-zh 即可。

对于中国大陆境内的服务器用户,可以使用 https://git.coding.net/larryli/gitlab.git 镜像(镜像手工同步,比 gitlab.com 上略有延迟)。

另外也可以在 Install Gems 步骤中使用 https://gems.ruby-china.org/ 镜像加快 gems 安装。具体步骤如下:

cd /home/gitlab
sudo -u git -H bundle config mirror.https://rubygems.org https://gems.ruby-china.org/

# For PostgreSQL (note, the option says "without ... mysql")
sudo -u git -H bundle install --deployment --without development test mysql aws kerberos

# Or if you use MySQL (note, the option says "without ... postgres")
sudo -u git -H bundle install --deployment --without development test postgres aws kerberos

对于升级操作也可以按照相应的 update.md 类似处理即可。

官方推荐的 Omnibus 安装

请先使用官方包安装或升级完成,确认当前版本。

sudo cat /opt/gitlab/embedded/service/gitlab-rails/VERSION

并确认当前汉化版本的 VERSION 是否相同。

如果安装版本小于当前汉化版本,请先升级。如果安装版本大于当前汉化版本,请在本项目中提交新的 issue

如果版本相同,首先在本地 clone 仓库。

# GitLab.com 仓库
git clone https://gitlab.com/larryli/gitlab.git

# 或 GitCafe.com 镜像
git clone https://git.coding.net/larryli/gitlab.git

然后比较汉化分支和原分支,导出 patch 用的 diff 文件。

# 7.13 版本的汉化补丁
git diff origin/7-13-stable..7-13-zh > ../7.13.diff

然后上传 7.13.diff 文件到服务器。

# 停止 gitlab
sudo gitlab-ctl stop
sudo patch -D /opt/gitlab/embedded/service/gitlab-rails -p1 < 7.13.diff

确定没有 .rej 文件,重启 GitLab 即可。

sudo gitlab-ctl start

如果汉化中出现问题,请重新安装 GitLab(注意备份数据)。

如何干掉 Windows7/8 上的“获取 Windows 10”提示

如果还没看到,可以选择关闭自动更新,选择手动或者自动下载。在安装更新的时候跳过 KB3035583 即可。

否则的话,请点击“开始”菜单,再点击“控制面板”,找到“程序”下面的“卸载程序”。在左侧点击“查看已安装的更新”,在下面的“Microsoft Windows”组里面找到 KB3035583(如果发现的早,通常排在该组最上面)。右键“卸载”。

PuTTY 和 GitLab 的中文汉化版均更新到最新

从年后开始中断了三个月,终于恢复了。
期间因为 Google Code 的关闭,PuTTY 直接转移到 GitHub,同时也开始采用 GitLab 的本地化工作流
GitLab 跳过了好几个版本,包括一个未完成的 7.8 版本。其中 7.8 升级到 7.9 安装文档增加了 nodejs 的安装。
因为版本变动大,没有去跟踪变更,也没有仔细遍历文件。所以遗漏会很多。如果发现问题可以提交到 7.11的汉化里程碑中,会一直维护到下一个分支版本发布,也就是下个月 22 号。
这也希望能形成一种惯例。😁

Laravel 5 系统架构:服务提供者、服务容器、Contracts、Facades

官方文档在一方面真的写的很糟糕,完全没描述相互之间的关系。

事实上,服务提供者(Service Provider)服务容器(Service Container)ContractsFacades 是一件东西的多个方面。其实就是 Yii 的组件(Component)。

和 Yii 一样,Laravel 的所有功能都是通过与 Yii 组件类似的服务架构来实现的。

一个最简单直接的例子是 Lavarel Hashing 服务。其自身只是一个 Bcrypt hash 的封装。

首先,Laravel 5 定义了一个 Contracts:Illuminate/Contracts/Hashing/Hasher.php

<?php namespace IlluminateContractsHashing;
interface Hasher {
    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array   $options
     * @return string
     */
    public function make($value, array $options = array());
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = array());
    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = array());
}

Contracts 只是描述一个服务的具体方法,而不是实现该服务。Container 才是服务的具体实现,对于一个服务来说,可能会有多种不同的实现。Hash 目前只有一个,就是:Illuminate/Hashing/BcryptHasher.php

<?php namespace IlluminateHashing;
use RuntimeException;
use IlluminateContractsHashingHasher as HasherContract;
class BcryptHasher implements HasherContract {
    /**
     * Default crypt cost factor.
     *
     * @var int
     */
    protected $rounds = 10;
    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array   $options
     * @return string
     *
     * @throws RuntimeException
     */
    public function make($value, array $options = array())
    {
        $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
        $hash = password_hash($value, PASSWORD_BCRYPT, array('cost' => $cost));
        if ($hash === false)
        {
            throw new RuntimeException("Bcrypt hashing not supported.");
        }
        return $hash;
    }
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = array())
    {
        return password_verify($value, $hashedValue);
    }
    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = array())
    {
        $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
        return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, array('cost' => $cost));
    }
    /**
     * Set the default password work factor.
     *
     * @param  int  $rounds
     * @return $this
     */
    public function setRounds($rounds)
    {
        $this->rounds = (int) $rounds;
        return $this;
    }
}

除了实现 Contracts 中定义的三个方法外,还实现了一个额外 setRounds() 方法。

下一步就是注册为服务提供者了:Illuminate/Hashing/HashServiceProvider.php

<?php namespace IlluminateHashing;
use IlluminateSupportServiceProvider;
class HashServiceProvider extends ServiceProvider {
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('hash', function() { return new BcryptHasher; });
    }
    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array('hash');
    }
}

很明显的 register 了服务,具体做的就是 new 了一个 Container。如果容器有多种实现,这时建议使用 Config 来配置服务了。

$this->app->singleton('hash', function($app) { return new Hasher($app['config']['hash']); });

这时候 Hasher 实际上一个工厂类(Factory),使用配置来确定具体的,如 new Hasher('bcrypt') 来实例化。

如果服务要延迟载入,也就是按需载入。需要有一个激活标志,也就是 provides() 方法。

当然,HashServiceProvider.php 必须在 config/app.phpproviders 中定义 'IlluminateHashingHashServiceProvider' 行。

这时,就可以使用:

$this->app->make('hash')->make('password');
$this->app['hash']->make('password');

最后,声明一个 Facade:Illuminate/Support/Facades/Hash.php

<?php namespace IlluminateSupportFacades;
/**
 * @see IlluminateHashingBcryptHasher
 */
class Hash extends Facade {
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'hash';
    }
}

config/app.phpaliases 中定义 'Hash' => 'IlluminateSupportFacadesHash' 行。

那么,就不用上面长长的 $this->app->make('hash'),直接:

Hash::make('password');

清除 chrome 的 HSTS 307 缓存

现代浏览器和网页服务器都开始支持 HSTS 功能,即自动将不安全的 HTTP 请求使用 307 Internal Redirect 跳转到 HTTPS 请求。

但是,用户的第一次访问还是不安全的。简单的说,在发生 307 的时候,用户可能就已经将用户名和密码暴露了。因为此时还是 HTTP 请求。

而且,这个第一次还可能是每次浏览器启动的时候,也就是说,可能每天都会发生,甚至多次。

所以,chrome 会自动记住每个域的 HSTS 设置。使得 HSTS 只是理论上的第一次暴露,后来就不经网页服务器返回,浏览器查询本地数据,直接伪造 HSTS 307 跳转到安全的 HTTPS,避免敏感数据的暴露。

很遗憾的说,这个很贴心的功能有时会造成麻烦。也就是说,一旦网页服务器设置了 HSTS,浏览器记住了。那么,将不会再访问该域的 HTTP 了。即使是网页服务器端已经修改了相关配置。原因当然是,这个记住是浏览器本地的。

还好,chrome 有暴露一些内部信息。不然就只有彻底删除浏览器才能清除。。。

在地址栏打开 chrome://net-internals/#hsts 下方的 Query domain 可以查询指定的域是否有 HSTS 记录,存在的话,在 Delete domain 删除即可。

GitLab 汉化工作流

首先,GitLab 汉化是针对 release 分支,而不是 master 分支。GitLab 的开发工作流是采用自身推荐的 GitLab Flow 开发工作流;简单的说,就是 master 分支用于开发新功能,发布版本到 release 分支(即 major-stable 分支);修补版本直接在 release 上迭代。

所以,GitLab 每个 release 版本的汉化在 git 树上独立的、不交叉的。即,7.1 分支上的汉化提交不会直接合并到 7.2 分支上,每个汉化 release 分支来源都是纯净的(pure)。

翻译的继承采用的是前一个 release 分支的 diff 然后 patch 到下一个版本的汉化 release 分支。

大致工作流程如下:

1、开始翻译项目

1
2
3
4
5
6
7
8
9
10
11
# 克隆源代码
git clone https://gitlab.com/gitlab-org/gitlab-ce.git
cd gitlab-ce
# 检出 7.1 版本分支
git checkout 7-1-stable
# 创建 7.1 版本汉化分支并切换工作区
git checkout -b 7-1-zh
# 开始翻译
nano app/views/layouts/_head.html.haml
git add app/views/layouts/_head.html.haml
git commit -m "translate layout head"

2、小版本更新,从 7.1.0 升级到 7.1.1

1
2
3
4
5
6
7
8
9
# 检出当前的 7.1 版本分支
git checkout 7-1-stable
# 拉取最新版本
git pull
# 然后切回当前翻译的 7.1 版本汉化分支
git checkout 7-1-zh
# 合并最新的版本
git merge 7-1-stable
# 如果发生冲突,手工处理合并冲突后再提交

3、大版本更新,从 7.1.x 升级到 7.2.x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 导出前一版本 7.1 分支的差异文件
git diff 7-1-stable..7-1-zh &gt; ../7-1.diff
# 获取新分支
git fetch origin
# 检出新的 7.2 版本分支
git checkout 7-2-stable
# 创建 7.2 版本汉化分支并切换工作区
git checkout -b 7-2-zh
# 应用差异文件
git apply ../7-1.diff
# 将成功的修改提交
git add app/views/layouts/_head.html.haml
git commit -m "import 7.1 translate"
# 如果 patch 产生了 .rej 文件,作为 todo 处理未成功 patch 的 diff 差异
nano app/views/layouts/_head.html.haml
git add app/views/layouts/_head.html.haml
git commit -m "translate layout head"
# 处理完所有 .rej 并删除,并检视相关代码继续翻译

步骤 2 和 3 可交替进行互不影响。原则上,新的大版本工作开始之后就不再维护旧版本分支的翻译;即,只翻译最新的内容。

优点:

  1. git 版本树干净;确保翻译分支会污染原有分支和其他的翻译分支
  2. 每个 release 版本只要源分支出现就有可用的翻译,并开始新版本翻译工作
  3. 官方的原生修改无需 review 代码审查,避免合并多个分支造成代码审查必须处理官方正常的变更
  4. 应用上一版本的翻译提交可以简单审查;因为只是“正确”的提交,而不是可能有人工参与的合并(隐含冲突处理)
  5. 应用上一版本的翻译的冲突 .rej 实际上是当前版本翻译的 todo,可以多人协作处理,而不用等待某次冲突处理合并完成再开始
  6. 如果资源足够,多个大版本的翻译可以并行;并且仍然可以通过 patch 在各分支中共享

缺点:

  1. patch 会丢掉原始的提交信息;新版本分支的 blame 审查将找不到原始的作者

GitLab 汉化版升级到 7.2.1

晚了一个多星期,期间 gitlab 还做了一个小版本修复。

7.2 比 7.1 多了 Star 功能,暂时翻译成“标记”了。另外,还发现 git 的 tag 和 issue 和 label 都是“标签”,这种翻译好头痛。

除了 github 外,现在还推送到国内的 gitcafe 做镜像。