Merge branch 'winh-translate-contributors-page-dates' into 'master'

Translate contributors page dates

Closes #39283 and #38592

See merge request gitlab-org/gitlab-ce!15846
This commit is contained in:
Filipa Lacerda 2017-12-18 11:19:14 +00:00
commit c20720cf84
10 changed files with 210 additions and 14 deletions

View file

@ -1,13 +1,14 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
import _ from 'underscore';
import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
import { n__ } from '../locale';
import { n__, s__, createDateTimeFormat, sprintf } from '../locale';
export default (function() {
function ContributorsStatGraph() {}
function ContributorsStatGraph() {
this.dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
}
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
@ -95,11 +96,15 @@ export default (function() {
};
ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain;
x_domain = ContributorsGraph.prototype.x_domain;
print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
return $("#date_header").text(print);
const x_domain = ContributorsGraph.prototype.x_domain;
const formattedDateRange = sprintf(
s__('ContributorsPage|%{startDate} %{endDate}'),
{
startDate: this.dateFormat.format(new Date(x_domain[0])),
endDate: this.dateFormat.format(new Date(x_domain[1])),
},
);
return $('#date_header').text(formattedDateRange);
};
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {

View file

@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
import _ from 'underscore';
import d3 from 'd3';
import { dateTickFormat } from '../lib/utils/tick_formats';
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
const hasProp = {}.hasOwnProperty;
@ -93,9 +94,12 @@ export const ContributorsMasterGraph = (function(superClass) {
extend(ContributorsMasterGraph, superClass);
function ContributorsMasterGraph(data1) {
const $parentElement = $('#contributors-master');
const parentPadding = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
this.data = data1;
this.update_content = this.update_content.bind(this);
this.width = $('.content').width() - 70;
this.width = $('.content').width() - parentPadding - (this.MARGIN.left + this.MARGIN.right);
this.height = 200;
this.x = null;
this.y = null;
@ -131,7 +135,10 @@ export const ContributorsMasterGraph = (function(superClass) {
};
ContributorsMasterGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
this.x_axis = d3.svg.axis()
.scale(this.x)
.orient('bottom')
.tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};
@ -219,7 +226,11 @@ export const ContributorsAuthorGraph = (function(superClass) {
};
ContributorsAuthorGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
this.x_axis = d3.svg.axis()
.scale(this.x)
.orient('bottom')
.ticks(8)
.tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};

View file

@ -0,0 +1,39 @@
import { createDateTimeFormat } from '../../locale';
let dateTimeFormats;
export const initDateFormats = () => {
const dayFormat = createDateTimeFormat({ month: 'short', day: 'numeric' });
const monthFormat = createDateTimeFormat({ month: 'long' });
const yearFormat = createDateTimeFormat({ year: 'numeric' });
dateTimeFormats = {
dayFormat,
monthFormat,
yearFormat,
};
};
initDateFormats();
/**
Formats a localized date in way that it can be used for d3.js axis.tickFormat().
That is, it displays
- 4-digit for first of January
- full month name for first of every month
- day and abbreviated month otherwise
see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
*/
export const dateTickFormat = (date) => {
if (date.getDate() !== 1) {
return dateTimeFormats.dayFormat.format(date);
}
if (date.getMonth() > 0) {
return dateTimeFormats.monthFormat.format(date);
}
return dateTimeFormats.yearFormat.format(date);
};

View file

@ -1,8 +1,7 @@
import Jed from 'jed';
import sprintf from './sprintf';
const langAttribute = document.querySelector('html').getAttribute('lang');
const lang = (langAttribute || 'en').replace(/-/g, '_');
const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en';
const locale = new Jed(window.translations || {});
delete window.translations;
@ -47,9 +46,19 @@ const pgettext = (keyOrContext, key) => {
return translated[translated.length - 1];
};
export { lang };
/**
Creates an instance of Intl.DateTimeFormat for the current locale.
@param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
@returns {Intl.DateTimeFormat}
*/
const createDateTimeFormat =
formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions);
export { languageCode };
export { gettext as __ };
export { ngettext as n__ };
export { pgettext as s__ };
export { sprintf };
export { createDateTimeFormat };
export default locale;

View file

@ -0,0 +1,5 @@
---
title: Translate date ranges on contributors page
merge_request: 15846
author:
type: changed

View file

@ -262,6 +262,21 @@ Sometimes you need to add some context to the text that you want to translate
s__('OpenedNDaysAgo|Opened')
```
### Dates / times
- In JavaScript:
```js
import { createDateTimeFormat } from '.../locale';
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
console.log(dateFormat.format(new Date('2063-04-05'))) // April 5, 2063
```
This makes use of [`Intl.DateTimeFormat`].
[`Intl.DateTimeFormat`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
## Adding a new language
Let's suppose you want to add translations for a new language, let's say French.

View file

@ -0,0 +1,26 @@
import ContributorsStatGraph from '~/graphs/stat_graph_contributors';
import { ContributorsGraph } from '~/graphs/stat_graph_contributors_graph';
import { setLanguage } from '../helpers/locale_helper';
describe('ContributorsStatGraph', () => {
describe('change_date_header', () => {
beforeAll(() => {
setLanguage('de');
});
afterAll(() => {
setLanguage(null);
});
it('uses the locale to display date ranges', () => {
ContributorsGraph.init_x_domain([{ date: '2013-01-31' }, { date: '2012-01-31' }]);
setFixtures('<div id="date_header"></div>');
const graph = new ContributorsStatGraph();
graph.change_date_header();
expect(document.getElementById('date_header').innerText).toBe('31. Januar 2012 31. Januar 2013');
});
});
});

View file

@ -0,0 +1,11 @@
/* eslint-disable import/prefer-default-export */
export const setLanguage = (languageCode) => {
const htmlElement = document.querySelector('html');
if (languageCode) {
htmlElement.setAttribute('lang', languageCode);
} else {
htmlElement.removeAttribute('lang');
}
};

View file

@ -0,0 +1,40 @@
import { dateTickFormat, initDateFormats } from '~/lib/utils/tick_formats';
import { setLanguage } from '../../helpers/locale_helper';
describe('tick formats', () => {
describe('dateTickFormat', () => {
beforeAll(() => {
setLanguage('de');
initDateFormats();
});
afterAll(() => {
setLanguage(null);
});
it('returns year for first of January', () => {
const tick = dateTickFormat(new Date('2001-01-01'));
expect(tick).toBe('2001');
});
it('returns month for first of February', () => {
const tick = dateTickFormat(new Date('2001-02-01'));
expect(tick).toBe('Februar');
});
it('returns day and month for second of February', () => {
const tick = dateTickFormat(new Date('2001-02-02'));
expect(tick).toBe('2. Feb.');
});
it('ignores time', () => {
const tick = dateTickFormat(new Date('2001-02-02 12:34:56'));
expect(tick).toBe('2. Feb.');
});
});
});

View file

@ -0,0 +1,35 @@
import { createDateTimeFormat, languageCode } from '~/locale';
import { setLanguage } from '../helpers/locale_helper';
describe('locale', () => {
afterEach(() => {
setLanguage(null);
});
describe('languageCode', () => {
it('parses the lang attribute', () => {
setLanguage('ja');
expect(languageCode()).toBe('ja');
});
it('falls back to English', () => {
setLanguage(null);
expect(languageCode()).toBe('en');
});
});
describe('createDateTimeFormat', () => {
beforeEach(() => {
setLanguage('de');
});
it('creates an instance of Intl.DateTimeFormat', () => {
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
expect(dateFormat.format(new Date(2015, 6, 3))).toBe('3. Juli 2015');
});
});
});