I18n update; also apply special CSS to iframe

This commit is contained in:
NaitLee 2022-04-25 23:47:30 +08:00
parent b97fd7022e
commit bf81524083
10 changed files with 178 additions and 65 deletions

View File

@ -79,9 +79,7 @@ If there are something better to organize these, feel free to discuss in issue.
- Bundled all required scripts, see file `0-transpile.sh`
- Is not there by default. Transpile it yourself
- `www/i18n*` - Scripts about I18n:
- TODO. In fact it worth a dedicated document to describe it
- (Mostly) Depends on "extensions" to work in the correct way,
feel free to extend, as it's *your* turn
- See [i18n.md](i18n.i18n/i18n.md)
- `www/*.js` - Other scripts:
- Small but useful, just look at them directly
- `www/jslicense.html` - Dedicated JavaScript License information

View File

@ -7,10 +7,12 @@ Currently User Interface related files are stored in `www/lang`
As well as `readme.i18n` and `i18n.i18n` for readme.
In the future, there can be other manual files.
*Note: I'm thinking about putting this part to dedicated repo.*
## What to do
In simple cases, you just make a copy of already-there language file, and modify it to your local language.
You should know what's your locale "code", for example "English (US)" is `en-US`. You can look at your browser locale configuration, or gather on the Web.
You should know what's your locale "code", for example "English (US)" is `en-US`. You can look at your browser locale configuration, or gather from the Web.
After that, add an entry in `list.json`.
## It seems can't satisfy another grammar
@ -23,7 +25,22 @@ Instead the grammar details is all done by code.
That doesn't mean difficulty:
- You can simply describe how should the grammar work, by communicating with natual language, with someone that could write code, at here or around you.
- At here almost everyone could do programming. Coding grammar needs much less work -- it's what a junior programmer could do to improve his/her skill.
- Here almost everyone could do programming. Coding grammar needs much less work -- it's what a junior programmer could do to improve his/her skill.
- Don't forget me! There are Issue and Discussion.
- Oh you actually can do code? Look at file `www/i18n-ext.js` then you could get the point. Also see `www/i18n.d.ts` for typing details. You may modify both.
- Or you actually can do code? Look at file `www/i18n-ext.js` then you could get the point. Also see `www/i18n.d.ts` for typing details. You may modify *anything*, **to be better**.
- You can also make "example" files (like `en-US-ex.jsonc`), for testing grammar points, or showing what could be done.
The `jsonc` format is JSON allowing comments. At the moment please use only `//`
- Use a test file:
- Load project Web Interface
- Open DevTools in browser (usually press F12)
- go to console, inside type like:
```js
testI18n('en-US'); // lang code
i18n('0-apples', [1]); // key ('conditions') and values ('things')
// ...
```
- See if it's correct

View File

@ -35,9 +35,9 @@
<span>Copyright &copy; 2021-2022 NaitLee Soft.</span>
<span data-i18n="some-rights-reserved">Some rights reserved.</span>
</p>
<p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>
<p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>
<p>You should have received a copy of the GNU General Public License along with this program. If not, see &lt;<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>&gt;.</p>
<p class="force-ltr">This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>
<p class="force-ltr">This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>
<p class="force-ltr">You should have received a copy of the GNU General Public License along with this program. If not, see &lt;<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>&gt;.</p>
</div>
</main>
</body>

View File

@ -9,53 +9,118 @@ var I18nExtensions = (function() {
// Don't forget to register your extension first!
var registers = {
'en-US': english,
'zh-CN': chinese,
'de-DE': german
'en-US': English,
'zh-CN': Chinese,
'de-DE': German,
'ar': Arabic
};
/**
* Here's especially useful for showing what can be done!
* @type {ExtensionOf<'en-US'>}
*/
function english(things, conditions) {
let text = conditions;
for (let index in things) {
let value = things[index];
if (value == 1) text = conditions['single'];
else text = conditions['multiple'];
if (!text && typeof value === 'number') {
if (value < 10 || value > 20) {
if (value % 10 === 1) text = conditions['1st'];
else if (value % 10 === 2) text = conditions['2nd'];
else if (value % 10 === 3) text = conditions['3rd'];
else text = conditions['nth'];
} else text = conditions['nth'];
function English(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let key in things) {
let value = things[key];
if (typeof value === 'number') {
if (conditions['nth']) {
// You can also replace what would be shown!
if (value < 10 || value > 20) {
if (value % 10 === 1) things[key] = value + 'st';
else if (value % 10 === 2) things[key] = value + 'nd';
else if (value % 10 === 3) things[key] = value + 'rd';
else things[key] = value + 'th';
} else things[key] = value + 'th';
return conditions['nth'];
} else {
if (value == 1) return conditions['single'];
else return conditions['multiple'];
}
} else {
if (conditions['an']) {
if ('aeiouAEIOU'.includes(value[0]))
things[key] = 'an ' + things[key];
else things[key] = 'a ' + things[key];
return conditions['an'];
}
}
// There are many thing else. Eg. Floor counting of en-US versus en-GB
// But in a project we should take just enough
}
return text;
}
/**
* 精辟不解释
* 不过量词什么的以后再说
* @type {ExtensionOf<'zh-CN'>}
*/
function chinese(things, conditions) {
return conditions;
function Chinese(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let key in things) {
function prepend(value) { things[key] = value + things[key]; }
let value = things[key];
if (typeof value === 'number') {
// 精辟。不解释。
return conditions;
} else {
if (conditions['measure']) {
// 不过要说量词的话……
switch (value) {
case '狗':
case '蛇':
prepend('条'); break;
case '猫':
case '鸡':
prepend('只'); break;
case '牛':
case '猪':
prepend('头'); break;
case '马':
case '狼':
prepend('匹'); break;
case '香蕉':
case '烧烤':
prepend('串'); break;
case '股债':
prepend('屁'); break;
default:
prepend('个');
}
// 还有很多。最好放在另一个文件
return conditions['measure'];
}
}
}
}
/**
* @type {ExtensionOf<'de-DE'>}
*/
function german(things, conditions) {
let text = conditions;
function German(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let index in things) {
let value = things[index];
if (value == 1) text = conditions['single'];
else text = conditions['multiple'];
if (typeof value === 'number') {
if (value == 1) return conditions['single'];
else return conditions['multiple'];
}
}
return text;
}
/**
* @type {ExtensionOf<'ar'>}
*/
function Arabic(things, conditions) {
// Another example of replacement
// const arabic_numbers = '٠١٢٣٤٥٦٧٨٩';
for (let key in things) {
let value = things[key];
if (typeof value === 'number')
things[key] = value.toLocaleString('ar');
}
return conditions;
}
return registers;

13
www/i18n.d.ts vendored
View File

@ -8,7 +8,7 @@ interface Extension {
(things: Things, conditions: Conditions): string;
}
interface ExtensionOf<K extends Languages> {
(things: Things, conditions: ConditionsOf<K>): string;
(things: Things, conditions: ConditionsOf<K> | string): string;
}
type Languages = keyof AllConditions;
@ -21,16 +21,17 @@ type AllConditions = {
'en-US': {
'single': string,
'multiple': string,
'1st': string,
'2nd': string,
'3rd': string,
'nth': string
'nth': string,
'a': string,
'an': string
},
'de-DE': {
'single': string,
'multiple': string
},
'zh-CN': {}
'zh-CN': {
'measure': string
}
};
interface I18nCallable extends I18n {

View File

@ -68,13 +68,11 @@ class I18n {
* Translate a string ("text"), using "things" such as numbers
* @param {string} text
* @param {Things} things
* @param {boolean} can_change_things
*/
translate(text, things, can_change_things = true) {
translate(text, things) {
let conditions = this.database[this.language][text] || text;
if (!things) return conditions;
if (!can_change_things) things = { ... things };
if (this.extensions[this.language] && typeof conditions !== 'string')
if (this.extensions[this.language])
text = this.extensions[this.language](things, conditions);
else text = conditions;
for (let key in things) {
@ -85,7 +83,7 @@ class I18n {
}
/**
* A i18n instance that is directly callable
* An i18n instance that is directly callable
* @type {I18nCallable}
*/
var i18n = (function() {
@ -95,10 +93,9 @@ var i18n = (function() {
/**
* @param {string} text
* @param {Things} things
* @param {boolean} can_change_things
*/
let i18n_callable = function(text, things, can_change_things = true) {
return instance.translate.call(i18n_callable, text, things, can_change_things);
let i18n_callable = function(text, things) {
return instance.translate.call(i18n_callable, text, things);
}
Object.setPrototypeOf(i18n_callable, instance);

16
www/lang/en-US-ex.jsonc Normal file
View File

@ -0,0 +1,16 @@
{
"$language": "English (US) i18n example",
// plural
"0-apples": {
"single": "{0} apple",
"multiple": "{0} apples"
},
// order
"0th-apple": {
"nth": "{0} apple"
},
// a or an
"there-is-a-0": {
"an": "There is {0}"
}
}

10
www/lang/zh-CN-ex.jsonc Normal file
View File

@ -0,0 +1,10 @@
{
"$language": "中文(简体)国际化样例",
//
"0-apples": "{0} 个苹果",
"0th-apple": "第 {0} 个苹果",
//
"there-is-a-0": {
"measure": "有一{0}"
}
}

View File

@ -52,11 +52,12 @@ a+a {
.center {
text-align: center;
}
button, input, select, textarea {
button, input, select, textarea, label {
font: inherit;
color: var(--fore-color);
/* background-color: var(--back-color); */
background-color: transparent;
display: inline-block;
}
select[multiple] {
width: 8em;
@ -70,7 +71,6 @@ button, input[type="number"], input[type="text"], select {
transition: all var(--anim-time) var(--curve);
cursor: pointer;
min-width: 6em;
display: inline-block;
line-height: calc(var(--font-size) + var(--span));
}
input[type="number"], input[type="text"] {
@ -491,10 +491,6 @@ a {
a:hover, a:active { color: #77f; }
canvas#preview, canvas#control-canvas,
#control-document, .logo { filter: brightness(0.6); }
body.high-contrast.dark {
--fore-color: #fff;
--back-color: #000;
}
}
/* so silly... */
body.dark { --fore-color: #eee; --back-color: #333; }
@ -533,6 +529,9 @@ body.force-rtl,
#force-rtl+label {
direction: rtl;
}
.force-ltr {
direction: ltr;
}
body.high-contrast {
--border: 2px;
--fore-color: #fff;

View File

@ -447,6 +447,14 @@ async function initI18n() {
}
}
async function testI18n(lang) {
i18n.useLanguage(lang);
i18n.add(lang, await fetch(`/lang/${lang}-ex.jsonc`)
.then(r => r.text()) // jsonc: JSON with comment
.then(t => JSON.parse(t.replace(/\s*\/\/.*/g, '')))
, true);
}
class Main {
promise;
/** @type {CanvasController} */
@ -472,8 +480,15 @@ class Main {
/** @type {HTMLIFrameElement} */
let iframe = document.getElementById('frame');
iframe.addEventListener('load', () => {
iframe.contentDocument.body.classList.value = document.body.classList.value;
applyI18nToDom(iframe.contentDocument);
});
function apply_class(class_name, value) {
[document, iframe.contentDocument].forEach(d => value ?
d.body.classList.add(class_name) :
d.body.classList.remove(class_name)
);
}
this.canvasController = new CanvasController();
putEvent('#button-exit', 'click', this.exit, this);
putEvent('#button-print', 'click', this.print, this);
@ -489,24 +504,19 @@ class Main {
(checked) => checked && Notice.note('dry-run-test-print-process-only')
);
this.attachSetter('#no-animation', 'change', 'no_animation',
(checked) => checked ? document.body.classList.add('no-animation')
: document.body.classList.remove('no-animation')
(checked) => apply_class('no-animation', checked)
);
this.attachSetter('#large-font', 'change', 'large_font',
(checked) => checked ? document.body.classList.add('large-font')
: document.body.classList.remove('large-font')
(checked) => apply_class('large-font', checked)
);
this.attachSetter('#force-rtl', 'change', 'force_rtl',
(checked) => checked ? document.body.classList.add('force-rtl')
: document.body.classList.remove('force-rtl')
(checked) => apply_class('force-rtl', checked)
);
this.attachSetter('#dark-theme', 'change', 'dark_theme',
(checked) => checked ? document.body.classList.add('dark')
: document.body.classList.remove('dark')
(checked) => apply_class('dark', checked)
);
this.attachSetter('#high-contrast', 'change', 'high_contrast',
(checked) => checked ? document.body.classList.add('high-contrast')
: document.body.classList.remove('high-contrast')
(checked) => apply_class('high-contrast', checked)
);
this.attachSetter('#threshold', 'change', 'threshold',
(value) => this.canvasController.threshold = value