mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-06-01 07:00:20 -07:00
I18n update; also apply special CSS to iframe
This commit is contained in:
parent
b97fd7022e
commit
bf81524083
@ -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`
|
- Bundled all required scripts, see file `0-transpile.sh`
|
||||||
- Is not there by default. Transpile it yourself
|
- Is not there by default. Transpile it yourself
|
||||||
- `www/i18n*` - Scripts about I18n:
|
- `www/i18n*` - Scripts about I18n:
|
||||||
- TODO. In fact it worth a dedicated document to describe it
|
- See [i18n.md](i18n.i18n/i18n.md)
|
||||||
- (Mostly) Depends on "extensions" to work in the correct way,
|
|
||||||
feel free to extend, as it's *your* turn
|
|
||||||
- `www/*.js` - Other scripts:
|
- `www/*.js` - Other scripts:
|
||||||
- Small but useful, just look at them directly
|
- Small but useful, just look at them directly
|
||||||
- `www/jslicense.html` - Dedicated JavaScript License information
|
- `www/jslicense.html` - Dedicated JavaScript License information
|
||||||
|
@ -7,10 +7,12 @@ Currently User Interface related files are stored in `www/lang`
|
|||||||
As well as `readme.i18n` and `i18n.i18n` for readme.
|
As well as `readme.i18n` and `i18n.i18n` for readme.
|
||||||
In the future, there can be other manual files.
|
In the future, there can be other manual files.
|
||||||
|
|
||||||
|
*Note: I'm thinking about putting this part to dedicated repo.*
|
||||||
|
|
||||||
## What to do
|
## What to do
|
||||||
|
|
||||||
In simple cases, you just make a copy of already-there language file, and modify it to your local language.
|
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`.
|
After that, add an entry in `list.json`.
|
||||||
|
|
||||||
## It seems can't satisfy another grammar
|
## 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:
|
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.
|
- 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.
|
- 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
|
||||||
|
@ -35,9 +35,9 @@
|
|||||||
<span>Copyright © 2021-2022 NaitLee Soft.</span>
|
<span>Copyright © 2021-2022 NaitLee Soft.</span>
|
||||||
<span data-i18n="some-rights-reserved">Some rights reserved.</span>
|
<span data-i18n="some-rights-reserved">Some rights reserved.</span>
|
||||||
</p>
|
</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 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>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">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 <<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>>.</p>
|
<p class="force-ltr">You should have received a copy of the GNU General Public License along with this program. If not, see <<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>>.</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
117
www/i18n-ext.js
117
www/i18n-ext.js
@ -9,53 +9,118 @@ var I18nExtensions = (function() {
|
|||||||
|
|
||||||
// Don't forget to register your extension first!
|
// Don't forget to register your extension first!
|
||||||
var registers = {
|
var registers = {
|
||||||
'en-US': english,
|
'en-US': English,
|
||||||
'zh-CN': chinese,
|
'zh-CN': Chinese,
|
||||||
'de-DE': german
|
'de-DE': German,
|
||||||
|
'ar': Arabic
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here's especially useful for showing what can be done!
|
* Here's especially useful for showing what can be done!
|
||||||
* @type {ExtensionOf<'en-US'>}
|
* @type {ExtensionOf<'en-US'>}
|
||||||
*/
|
*/
|
||||||
function english(things, conditions) {
|
function English(things, conditions) {
|
||||||
let text = conditions;
|
if (typeof conditions === 'string')
|
||||||
for (let index in things) {
|
return conditions;
|
||||||
let value = things[index];
|
for (let key in things) {
|
||||||
if (value == 1) text = conditions['single'];
|
let value = things[key];
|
||||||
else text = conditions['multiple'];
|
if (typeof value === 'number') {
|
||||||
if (!text && typeof value === 'number') {
|
if (conditions['nth']) {
|
||||||
if (value < 10 || value > 20) {
|
// You can also replace what would be shown!
|
||||||
if (value % 10 === 1) text = conditions['1st'];
|
if (value < 10 || value > 20) {
|
||||||
else if (value % 10 === 2) text = conditions['2nd'];
|
if (value % 10 === 1) things[key] = value + 'st';
|
||||||
else if (value % 10 === 3) text = conditions['3rd'];
|
else if (value % 10 === 2) things[key] = value + 'nd';
|
||||||
else text = conditions['nth'];
|
else if (value % 10 === 3) things[key] = value + 'rd';
|
||||||
} else text = conditions['nth'];
|
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'>}
|
* @type {ExtensionOf<'zh-CN'>}
|
||||||
*/
|
*/
|
||||||
function chinese(things, conditions) {
|
function Chinese(things, conditions) {
|
||||||
return 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'>}
|
* @type {ExtensionOf<'de-DE'>}
|
||||||
*/
|
*/
|
||||||
function german(things, conditions) {
|
function German(things, conditions) {
|
||||||
let text = conditions;
|
if (typeof conditions === 'string')
|
||||||
|
return conditions;
|
||||||
for (let index in things) {
|
for (let index in things) {
|
||||||
let value = things[index];
|
let value = things[index];
|
||||||
if (value == 1) text = conditions['single'];
|
if (typeof value === 'number') {
|
||||||
else text = conditions['multiple'];
|
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;
|
return registers;
|
||||||
|
13
www/i18n.d.ts
vendored
13
www/i18n.d.ts
vendored
@ -8,7 +8,7 @@ interface Extension {
|
|||||||
(things: Things, conditions: Conditions): string;
|
(things: Things, conditions: Conditions): string;
|
||||||
}
|
}
|
||||||
interface ExtensionOf<K extends Languages> {
|
interface ExtensionOf<K extends Languages> {
|
||||||
(things: Things, conditions: ConditionsOf<K>): string;
|
(things: Things, conditions: ConditionsOf<K> | string): string;
|
||||||
}
|
}
|
||||||
type Languages = keyof AllConditions;
|
type Languages = keyof AllConditions;
|
||||||
|
|
||||||
@ -21,16 +21,17 @@ type AllConditions = {
|
|||||||
'en-US': {
|
'en-US': {
|
||||||
'single': string,
|
'single': string,
|
||||||
'multiple': string,
|
'multiple': string,
|
||||||
'1st': string,
|
'nth': string,
|
||||||
'2nd': string,
|
'a': string,
|
||||||
'3rd': string,
|
'an': string
|
||||||
'nth': string
|
|
||||||
},
|
},
|
||||||
'de-DE': {
|
'de-DE': {
|
||||||
'single': string,
|
'single': string,
|
||||||
'multiple': string
|
'multiple': string
|
||||||
},
|
},
|
||||||
'zh-CN': {}
|
'zh-CN': {
|
||||||
|
'measure': string
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface I18nCallable extends I18n {
|
interface I18nCallable extends I18n {
|
||||||
|
13
www/i18n.js
13
www/i18n.js
@ -68,13 +68,11 @@ class I18n {
|
|||||||
* Translate a string ("text"), using "things" such as numbers
|
* Translate a string ("text"), using "things" such as numbers
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {Things} things
|
* @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;
|
let conditions = this.database[this.language][text] || text;
|
||||||
if (!things) return conditions;
|
if (!things) return conditions;
|
||||||
if (!can_change_things) things = { ... things };
|
if (this.extensions[this.language])
|
||||||
if (this.extensions[this.language] && typeof conditions !== 'string')
|
|
||||||
text = this.extensions[this.language](things, conditions);
|
text = this.extensions[this.language](things, conditions);
|
||||||
else text = conditions;
|
else text = conditions;
|
||||||
for (let key in things) {
|
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}
|
* @type {I18nCallable}
|
||||||
*/
|
*/
|
||||||
var i18n = (function() {
|
var i18n = (function() {
|
||||||
@ -95,10 +93,9 @@ var i18n = (function() {
|
|||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {Things} things
|
* @param {Things} things
|
||||||
* @param {boolean} can_change_things
|
|
||||||
*/
|
*/
|
||||||
let i18n_callable = function(text, things, can_change_things = true) {
|
let i18n_callable = function(text, things) {
|
||||||
return instance.translate.call(i18n_callable, text, things, can_change_things);
|
return instance.translate.call(i18n_callable, text, things);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.setPrototypeOf(i18n_callable, instance);
|
Object.setPrototypeOf(i18n_callable, instance);
|
||||||
|
16
www/lang/en-US-ex.jsonc
Normal file
16
www/lang/en-US-ex.jsonc
Normal 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
10
www/lang/zh-CN-ex.jsonc
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$language": "中文(简体)国际化样例",
|
||||||
|
// 比起英文,这就简单了……
|
||||||
|
"0-apples": "{0} 个苹果",
|
||||||
|
"0th-apple": "第 {0} 个苹果",
|
||||||
|
// ……直到碰见量词
|
||||||
|
"there-is-a-0": {
|
||||||
|
"measure": "有一{0}"
|
||||||
|
}
|
||||||
|
}
|
11
www/main.css
11
www/main.css
@ -52,11 +52,12 @@ a+a {
|
|||||||
.center {
|
.center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
button, input, select, textarea {
|
button, input, select, textarea, label {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: var(--fore-color);
|
color: var(--fore-color);
|
||||||
/* background-color: var(--back-color); */
|
/* background-color: var(--back-color); */
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
select[multiple] {
|
select[multiple] {
|
||||||
width: 8em;
|
width: 8em;
|
||||||
@ -70,7 +71,6 @@ button, input[type="number"], input[type="text"], select {
|
|||||||
transition: all var(--anim-time) var(--curve);
|
transition: all var(--anim-time) var(--curve);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 6em;
|
min-width: 6em;
|
||||||
display: inline-block;
|
|
||||||
line-height: calc(var(--font-size) + var(--span));
|
line-height: calc(var(--font-size) + var(--span));
|
||||||
}
|
}
|
||||||
input[type="number"], input[type="text"] {
|
input[type="number"], input[type="text"] {
|
||||||
@ -491,10 +491,6 @@ a {
|
|||||||
a:hover, a:active { color: #77f; }
|
a:hover, a:active { color: #77f; }
|
||||||
canvas#preview, canvas#control-canvas,
|
canvas#preview, canvas#control-canvas,
|
||||||
#control-document, .logo { filter: brightness(0.6); }
|
#control-document, .logo { filter: brightness(0.6); }
|
||||||
body.high-contrast.dark {
|
|
||||||
--fore-color: #fff;
|
|
||||||
--back-color: #000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* so silly... */
|
/* so silly... */
|
||||||
body.dark { --fore-color: #eee; --back-color: #333; }
|
body.dark { --fore-color: #eee; --back-color: #333; }
|
||||||
@ -533,6 +529,9 @@ body.force-rtl,
|
|||||||
#force-rtl+label {
|
#force-rtl+label {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
|
.force-ltr {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
body.high-contrast {
|
body.high-contrast {
|
||||||
--border: 2px;
|
--border: 2px;
|
||||||
--fore-color: #fff;
|
--fore-color: #fff;
|
||||||
|
30
www/main.js
30
www/main.js
@ -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 {
|
class Main {
|
||||||
promise;
|
promise;
|
||||||
/** @type {CanvasController} */
|
/** @type {CanvasController} */
|
||||||
@ -472,8 +480,15 @@ class Main {
|
|||||||
/** @type {HTMLIFrameElement} */
|
/** @type {HTMLIFrameElement} */
|
||||||
let iframe = document.getElementById('frame');
|
let iframe = document.getElementById('frame');
|
||||||
iframe.addEventListener('load', () => {
|
iframe.addEventListener('load', () => {
|
||||||
|
iframe.contentDocument.body.classList.value = document.body.classList.value;
|
||||||
applyI18nToDom(iframe.contentDocument);
|
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();
|
this.canvasController = new CanvasController();
|
||||||
putEvent('#button-exit', 'click', this.exit, this);
|
putEvent('#button-exit', 'click', this.exit, this);
|
||||||
putEvent('#button-print', 'click', this.print, this);
|
putEvent('#button-print', 'click', this.print, this);
|
||||||
@ -489,24 +504,19 @@ class Main {
|
|||||||
(checked) => checked && Notice.note('dry-run-test-print-process-only')
|
(checked) => checked && Notice.note('dry-run-test-print-process-only')
|
||||||
);
|
);
|
||||||
this.attachSetter('#no-animation', 'change', 'no_animation',
|
this.attachSetter('#no-animation', 'change', 'no_animation',
|
||||||
(checked) => checked ? document.body.classList.add('no-animation')
|
(checked) => apply_class('no-animation', checked)
|
||||||
: document.body.classList.remove('no-animation')
|
|
||||||
);
|
);
|
||||||
this.attachSetter('#large-font', 'change', 'large_font',
|
this.attachSetter('#large-font', 'change', 'large_font',
|
||||||
(checked) => checked ? document.body.classList.add('large-font')
|
(checked) => apply_class('large-font', checked)
|
||||||
: document.body.classList.remove('large-font')
|
|
||||||
);
|
);
|
||||||
this.attachSetter('#force-rtl', 'change', 'force_rtl',
|
this.attachSetter('#force-rtl', 'change', 'force_rtl',
|
||||||
(checked) => checked ? document.body.classList.add('force-rtl')
|
(checked) => apply_class('force-rtl', checked)
|
||||||
: document.body.classList.remove('force-rtl')
|
|
||||||
);
|
);
|
||||||
this.attachSetter('#dark-theme', 'change', 'dark_theme',
|
this.attachSetter('#dark-theme', 'change', 'dark_theme',
|
||||||
(checked) => checked ? document.body.classList.add('dark')
|
(checked) => apply_class('dark', checked)
|
||||||
: document.body.classList.remove('dark')
|
|
||||||
);
|
);
|
||||||
this.attachSetter('#high-contrast', 'change', 'high_contrast',
|
this.attachSetter('#high-contrast', 'change', 'high_contrast',
|
||||||
(checked) => checked ? document.body.classList.add('high-contrast')
|
(checked) => apply_class('high-contrast', checked)
|
||||||
: document.body.classList.remove('high-contrast')
|
|
||||||
);
|
);
|
||||||
this.attachSetter('#threshold', 'change', 'threshold',
|
this.attachSetter('#threshold', 'change', 'threshold',
|
||||||
(value) => this.canvasController.threshold = value
|
(value) => this.canvasController.threshold = value
|
||||||
|
Loading…
x
Reference in New Issue
Block a user