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:
commit
c20720cf84
10 changed files with 210 additions and 14 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
39
app/assets/javascripts/lib/utils/tick_formats.js
Normal file
39
app/assets/javascripts/lib/utils/tick_formats.js
Normal 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);
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Translate date ranges on contributors page
|
||||
merge_request: 15846
|
||||
author:
|
||||
type: changed
|
|
@ -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.
|
||||
|
|
26
spec/javascripts/graphs/stat_graph_contributors_spec.js
Normal file
26
spec/javascripts/graphs/stat_graph_contributors_spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
11
spec/javascripts/helpers/locale_helper.js
Normal file
11
spec/javascripts/helpers/locale_helper.js
Normal 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');
|
||||
}
|
||||
};
|
40
spec/javascripts/lib/utils/tick_formats_spec.js
Normal file
40
spec/javascripts/lib/utils/tick_formats_spec.js
Normal 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.');
|
||||
});
|
||||
});
|
||||
});
|
35
spec/javascripts/locale/index_spec.js
Normal file
35
spec/javascripts/locale/index_spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue