You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
4 Commits
v3.3.11
...
plural_tra
Author | SHA1 | Date | |
---|---|---|---|
|
7b5224b46c | ||
|
9bb467a340 | ||
|
c0d6df7379 | ||
|
de7f5e7217 |
@@ -1,4 +1,4 @@
|
||||
import { closestSupportedLocale } from './locale';
|
||||
import { closestSupportedLocale, parsePluralForm, setLocale, _n } from './locale';
|
||||
|
||||
describe('locale', () => {
|
||||
|
||||
@@ -15,4 +15,80 @@ describe('locale', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should translate plurals - en_GB', () => {
|
||||
setLocale('en_GB');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Copy Shareable Link');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Copy Shareable Links');
|
||||
});
|
||||
|
||||
it('should translate plurals - fr_FR', () => {
|
||||
setLocale('fr_FR');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Copier lien partageable');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Copier liens partageables');
|
||||
});
|
||||
|
||||
it('should translate plurals - pl_PL', () => {
|
||||
setLocale('pl_PL');
|
||||
// Not the best test since 5 is the same as 2, but it's all I could find
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Kopiuj udostępnialny link');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Kopiuj udostępnialne linki');
|
||||
expect(_n('Copy Shareable Link', 'Copy Shareable Links', 5)).toBe('Kopiuj udostępnialne linki');
|
||||
});
|
||||
|
||||
it('should parse the plural form', async () => {
|
||||
const pluralForms = [
|
||||
'nplurals=1; plural=0;',
|
||||
'nplurals=2; plural=(n != 0);',
|
||||
'nplurals=2; plural=(n != 1);',
|
||||
'nplurals=2; plural=(n > 1);',
|
||||
'nplurals=2; plural=(n%10!=1 || n%100==11);',
|
||||
'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);',
|
||||
'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);',
|
||||
'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);',
|
||||
'nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2);',
|
||||
'nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);',
|
||||
'nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);',
|
||||
'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;',
|
||||
'nplurals=3; plural=(n==1) ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;',
|
||||
'nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);',
|
||||
'nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3);',
|
||||
'nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;',
|
||||
'nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;',
|
||||
'nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3;',
|
||||
'nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 :(n>6 && n<11) ? 3 : 4;',
|
||||
'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);',
|
||||
];
|
||||
|
||||
const pluralValues = [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1],
|
||||
[2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1],
|
||||
[2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1],
|
||||
[2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2],
|
||||
[0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||
[2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2],
|
||||
[2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||
[2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2],
|
||||
[0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3],
|
||||
[3, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
|
||||
[2, 0, 1, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||
[3, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
|
||||
[4, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
|
||||
[0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
|
||||
];
|
||||
|
||||
for (let index = 0; index < pluralForms.length; index++) {
|
||||
const form = pluralForms[index];
|
||||
const pluralFn = parsePluralForm(form);
|
||||
for (let i = 0; i < 128; ++i) {
|
||||
expect(pluralValues[index][i]).toBe(pluralFn(i));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -7,6 +7,7 @@ interface StringToStringMap {
|
||||
interface CodeToCountryMap {
|
||||
[key: string]: string[];
|
||||
}
|
||||
type ParsePluralFormFunction = (n: number)=> number;
|
||||
|
||||
const codeToLanguageE_: StringToStringMap = {};
|
||||
codeToLanguageE_['aa'] = 'Afar';
|
||||
@@ -436,12 +437,51 @@ const codeToCountry_: CodeToCountryMap = {
|
||||
let supportedLocales_: any = null;
|
||||
let localeStats_: any = null;
|
||||
|
||||
const loadedLocales_: any = {};
|
||||
const loadedLocales_: Record<string, Record<string, string[]>> = {};
|
||||
|
||||
const pluralFunctions_: Record<string, ParsePluralFormFunction> = {};
|
||||
|
||||
const defaultLocale_ = 'en_GB';
|
||||
|
||||
let currentLocale_ = defaultLocale_;
|
||||
|
||||
// Copied from https://github.com/eugeny-dementev/parse-gettext-plural-form
|
||||
// along with the tests
|
||||
export const parsePluralForm = (form: string): ParsePluralFormFunction => {
|
||||
const pluralFormRegex = /^(\s*nplurals\s*=\s*[0-9]+\s*;\s*plural\s*=\s*(?:\s|[-?|&=!<>+*/%:;a-zA-Z0-9_()])+)$/m;
|
||||
|
||||
if (!pluralFormRegex.test(form)) throw new Error(`Plural-Forms is invalid: ${form}`);
|
||||
|
||||
if (!/;\s*$/.test(form)) {
|
||||
form += ';';
|
||||
}
|
||||
|
||||
const code = [
|
||||
'var plural;',
|
||||
'var nplurals;',
|
||||
form,
|
||||
'return (plural === true ? 1 : plural ? plural : 0);',
|
||||
].join('\n');
|
||||
|
||||
// eslint-disable-next-line no-new-func -- There's a regex to check the form but it's still slighlty unsafe, eventually we should automatically generate all the functions in advance in build-translations.ts
|
||||
return (new Function('n', code)) as ParsePluralFormFunction;
|
||||
};
|
||||
|
||||
const getPluralFunction = (lang: string) => {
|
||||
if (!(lang in pluralFunctions_)) {
|
||||
const locale = closestSupportedLocale(lang);
|
||||
const stats = localeStats()[locale];
|
||||
|
||||
if (!stats.pluralForms) {
|
||||
pluralFunctions_[lang] = null;
|
||||
} else {
|
||||
pluralFunctions_[lang] = parsePluralForm(stats.pluralForms);
|
||||
}
|
||||
}
|
||||
|
||||
return pluralFunctions_[lang];
|
||||
};
|
||||
|
||||
function defaultLocale() {
|
||||
return defaultLocale_;
|
||||
}
|
||||
@@ -589,18 +629,45 @@ function _(s: string, ...args: any[]): string {
|
||||
}
|
||||
|
||||
function _n(singular: string, plural: string, n: number, ...args: any[]) {
|
||||
if (n > 1) return _(plural, ...args);
|
||||
return _(singular, ...args);
|
||||
if (['en_GB', 'en_US'].includes(currentLocale_)) {
|
||||
if (n > 1) return _(plural, ...args);
|
||||
return _(singular, ...args);
|
||||
} else {
|
||||
const pluralFn = getPluralFunction(currentLocale_);
|
||||
const stringIndex = pluralFn ? pluralFn(n) : 0;
|
||||
const strings = localeStrings(currentLocale_);
|
||||
const result = strings[singular];
|
||||
|
||||
let translatedString = '';
|
||||
if (result === undefined || !result.join('')) {
|
||||
translatedString = singular;
|
||||
} else {
|
||||
translatedString = stringIndex < result.length ? result[stringIndex] : result[0];
|
||||
}
|
||||
|
||||
try {
|
||||
return sprintf(translatedString, ...args);
|
||||
} catch (error) {
|
||||
return `${translatedString} ${args.join(', ')} (Translation error: ${error.message})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
|
||||
const strings = localeStrings(locale);
|
||||
let result = strings[s];
|
||||
if (result === '' || result === undefined) result = s;
|
||||
const result = strings[s];
|
||||
let translatedString = '';
|
||||
|
||||
if (result === undefined || !result.join('')) {
|
||||
translatedString = s;
|
||||
} else {
|
||||
translatedString = result[0];
|
||||
}
|
||||
|
||||
try {
|
||||
return sprintf(result, ...args);
|
||||
return sprintf(translatedString, ...args);
|
||||
} catch (error) {
|
||||
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
|
||||
return `${translatedString} ${args.join(', ')} (Translation error: ${error.message})`;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import { countryDisplayName, countryCodeOnly } from '@joplin/lib/locale';
|
||||
import { readdirSync, writeFileSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { copy, mkdirpSync, remove } from 'fs-extra';
|
||||
const { GettextExtractor, JsExtractors } = require('gettext-extractor');
|
||||
import { GettextExtractor, JsExtractors } from 'gettext-extractor';
|
||||
|
||||
const rootDir = `${__dirname}/../..`;
|
||||
const localesDir = `${__dirname}/locales`;
|
||||
@@ -20,7 +20,7 @@ const libDir = `${rootDir}/packages/lib`;
|
||||
|
||||
function serializeTranslation(translation: string) {
|
||||
const output = parseTranslations(translation);
|
||||
return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), ' ');
|
||||
return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), '\t');
|
||||
}
|
||||
|
||||
function saveToFile(filePath: string, data: string) {
|
||||
@@ -31,6 +31,7 @@ async function buildLocale(inputFile: string, outputFile: string) {
|
||||
const r = await parsePoFile(inputFile);
|
||||
const translation = serializeTranslation(r);
|
||||
saveToFile(outputFile, translation);
|
||||
return { headers: r.headers };
|
||||
}
|
||||
|
||||
async function createPotFile(potFilePath: string) {
|
||||
@@ -391,9 +392,10 @@ async function main() {
|
||||
const poFilePäth = `${localesDir}/${locale}.po`;
|
||||
const jsonFilePath = `${jsonLocalesDir}/${locale}.json`;
|
||||
if (locale !== defaultLocale) await mergePotToPo(potFilePath, poFilePäth);
|
||||
await buildLocale(poFilePäth, jsonFilePath);
|
||||
const { headers } = await buildLocale(poFilePäth, jsonFilePath);
|
||||
|
||||
const stat = await translationStatus(defaultLocale === locale, poFilePäth);
|
||||
stat.pluralForms = headers['Plural-Forms'];
|
||||
stat.locale = locale;
|
||||
stat.languageName = countryDisplayName(locale);
|
||||
stats.push(stat);
|
||||
|
@@ -8,9 +8,10 @@ export interface TranslationStatus {
|
||||
translatorName: string;
|
||||
percentDone: number;
|
||||
untranslatedCount: number;
|
||||
pluralForms?: string;
|
||||
}
|
||||
|
||||
export type Translations = Record<string, string>;
|
||||
export type Translations = Record<string, string[]>;
|
||||
|
||||
export const removePoHeaderDate = async (filePath: string) => {
|
||||
let sedPrefix = 'sed -i';
|
||||
@@ -67,14 +68,14 @@ export const parseTranslations = (gettextTranslations: any) => {
|
||||
if (!translations.hasOwnProperty(n)) continue;
|
||||
if (n === '') continue;
|
||||
const t = translations[n];
|
||||
let translated = '';
|
||||
let translated: string[] = [];
|
||||
if (t.comments && t.comments.flag && t.comments.flag.indexOf('fuzzy') >= 0) {
|
||||
// Don't include fuzzy translations
|
||||
} else {
|
||||
translated = t['msgstr'][0];
|
||||
translated = t['msgstr'];
|
||||
}
|
||||
|
||||
if (translated) output[n] = translated;
|
||||
if (translated.length) output[n] = translated;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ describe('applyTranslations', () => {
|
||||
{
|
||||
html: '<div><span translate>Translate me</span></div>',
|
||||
translations: {
|
||||
'Translate me': 'Traduis moi',
|
||||
'Translate me': ['Traduis moi'],
|
||||
},
|
||||
htmlTranslated: '<div>\n<span translate>\nTraduis moi\n</span>\n</div>',
|
||||
},
|
||||
@@ -19,14 +19,14 @@ describe('applyTranslations', () => {
|
||||
{
|
||||
html: '<h1 translate class="text-center">\nFree your <span class="frame-bg frame-bg-blue">notes</span>\n</h1>',
|
||||
translations: {
|
||||
'Free your <span class="frame-bg frame-bg-blue">notes</span>': 'Libérez vos <span class="frame-bg frame-bg-blue">notes</span>',
|
||||
'Free your <span class="frame-bg frame-bg-blue">notes</span>': ['Libérez vos <span class="frame-bg frame-bg-blue">notes</span>'],
|
||||
},
|
||||
htmlTranslated: '<h1 translate class="text-center">\nLibérez vos <span class="frame-bg frame-bg-blue">notes</span>\n</h1>',
|
||||
},
|
||||
{
|
||||
html: '<div translate>Save <span class="frame-bg frame-bg-blue">web pages</span> <br />as notes</div>',
|
||||
translations: {
|
||||
'Save <span class="frame-bg frame-bg-blue">web pages</span> <br>as notes': 'Sauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes',
|
||||
'Save <span class="frame-bg frame-bg-blue">web pages</span> <br>as notes': ['Sauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes'],
|
||||
},
|
||||
htmlTranslated: '<div translate>\nSauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes\n</div>',
|
||||
},
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { unique } from '@joplin/lib/ArrayUtils';
|
||||
import { attributesHtml, isSelfClosingTag } from '@joplin/renderer/htmlUtils';
|
||||
import { Translations } from '../../utils/translation';
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = new Entities().encode;
|
||||
const htmlparser2 = require('@joplin/fork-htmlparser2');
|
||||
@@ -15,7 +16,7 @@ const trimHtml = (content: string) => {
|
||||
.replace(/\t+$/, '');
|
||||
};
|
||||
|
||||
const findTranslation = (englishString: string, translations: Record<string, string>): string => {
|
||||
const findTranslation = (englishString: string, translations: Translations): string => {
|
||||
const stringsToTry = unique([
|
||||
englishString,
|
||||
englishString.replace(/<br\/>/gi, '<br>'),
|
||||
@@ -26,7 +27,8 @@ const findTranslation = (englishString: string, translations: Record<string, str
|
||||
]) as string[];
|
||||
|
||||
for (const stringToTry of stringsToTry) {
|
||||
if (translations[stringToTry]) return translations[stringToTry];
|
||||
// Note that we don't currently support plural forms for the website
|
||||
if (translations[stringToTry] && translations[stringToTry].length) return translations[stringToTry][0];
|
||||
}
|
||||
|
||||
return englishString;
|
||||
@@ -38,7 +40,7 @@ const encodeHtml = (decodedText: string): string => {
|
||||
.replace(/{{> /gi, '{{> '); // Don't break Mustache partials
|
||||
};
|
||||
|
||||
export default (html: string, _languageCode: string, translations: Record<string, string>) => {
|
||||
export default (html: string, _languageCode: string, translations: Translations) => {
|
||||
const output: string[] = [];
|
||||
|
||||
interface State {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { mkdirp, readFile, writeFile } from 'fs-extra';
|
||||
import { dirname } from 'path';
|
||||
import { Translations } from '../../utils/translation';
|
||||
import applyTranslations from './applyTranslations';
|
||||
|
||||
export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Record<string, string>) => {
|
||||
export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Translations) => {
|
||||
let content = await readFile(englishFilePath, 'utf8');
|
||||
content = content.replace('<html lang="en-gb">', `<html lang="${languageCode}">`);
|
||||
const translatedContent = await applyTranslations(content, languageCode, translations);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Plan, StripePublicConfig } from '@joplin/lib/utils/joplinCloud';
|
||||
import { Sponsors } from '../../utils/loadSponsors';
|
||||
import { Translations } from '../../utils/translation';
|
||||
import { OpenGraphTags } from './openGraph';
|
||||
|
||||
export enum Env {
|
||||
@@ -8,7 +9,7 @@ export enum Env {
|
||||
}
|
||||
|
||||
export interface Locale {
|
||||
htmlTranslations: Record<string, string>;
|
||||
htmlTranslations: Translations;
|
||||
lang: string;
|
||||
pathPrefix: string;
|
||||
}
|
||||
|
Reference in New Issue
Block a user