1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-02 21:02:49 +02:00

better localization

This commit is contained in:
Pomax
2020-08-08 11:21:51 -07:00
parent b13d8fe00d
commit 03216d1488
16 changed files with 285 additions and 136 deletions

View File

@@ -102,18 +102,42 @@ drawCurveCoordinates() {
}
onKeyDown() {
this.mark = false;
if (this.keyboard[`ArrowDown`]) {
this.step--;
if (this.step < 10) this.step = 10;
this.stepDown();
}
if (this.keyboard[`ArrowUp`]) {
this.step++;
if (this.step > 90) this.step = 90;
this.stepUp();
}
redraw();
}
stepDown(value = 1) {
this.step -= value;
if (this.step < 10) this.step = 10;
}
stepUp(value = 1) {
this.step += value;
if (this.step > 90) this.step = 90;
}
onMouseDown() {
this.mark = this.cursor.y;
}
onMouseUp() {
this.mark = false;
}
onMouseMove() {
this.curve.update();
if (this.mark) {
let diff = this.mark - this.cursor.y,
mapped = map(diff, -this.height/2, this.height/2, 10, 90, true);
this.step = mapped | 0;
}
redraw();
}

View File

@@ -279,6 +279,58 @@ class GraphicsAPI extends BaseAPI {
this.line({ x: 0, y }, { x: this.width, y });
}
}
/**
* math functions
*/
floor(v) {
return Math.floor(v);
}
ceil(v) {
return Math.ceil(v);
}
round(v) {
return Math.round(v);
}
abs(v) {
return Math.abs(v);
}
sin(v) {
return Math.sin(v);
}
cos(v) {
return Math.cos(v);
}
tan(v) {
return Math.tan(v);
}
sqrt(v) {
return Math.sqrt(v);
}
atan2(dy, dx) {
return Math.atan2(dy, dx);
}
pow(v, p) {
return Math.pow(v, p);
}
map(v, s, e, ns, ne, constrain = false) {
const i1 = e - s,
i2 = ne - ns,
p = v - s;
let r = ns + (p * i2) / i1;
if (constrain) r = r < ns ? ns : r > ne ? ne : r;
return r;
}
}
export { GraphicsAPI, Bezier, Vector };

View File

@@ -1,53 +0,0 @@
export default {
"defaultLocale": "en-GB",
"title": {
"en-GB": "A Primer on Bézier Curves",
"ja-JP": "ベジェ曲線入門",
"zh-CN": "贝塞尔曲线底漆"
},
"subtitle": {
"en-GB": "A free, online book for when you really need to know how to do Bézier things.",
"ja-JP": "A free, online book for when you really need to know how to do Bézier things.",
"zh-CN": "A free, online book for when you really need to know how to do Bézier things."
},
"description": {
"en-GB": "A detailed explanation of Bézier curves, and how to do the many things that we commonly want to do with them.",
"ja-JP": "A detailed explanation of Bézier curves, and how to do the many things that we commonly want to do with them.",
"zh-CN": "A detailed explanation of Bézier curves, and how to do the many things that we commonly want to do with them."
},
"tocLabel": {
"en-GB": "Table of Contents",
"ja-JP": "目次",
"zh-CN": "目录"
},
"localeName": {
"en-GB": "English",
"zh-CN": "中文",
"ja-JP": "日本語"
},
"langSwitchLabel": {
"en-GB": "Read this in your own language:",
"zh-CN": "Read this in your own language:",
"ja-JP": "Read this in your own language:"
},
"langHelpLabel": {
"en-GB": "Don't see your language listed? <a href=\"https://github.com/Pomax/BezierInfo-2/wiki/localize\">Help translate this content!</a>",
"zh-CN": "Don't see your language listed? <a href=\"https://github.com/Pomax/BezierInfo-2/wiki/localize\">Help translate this content!</a>",
"ja-JP": "Don't see your language listed? <a href=\"https://github.com/Pomax/BezierInfo-2/wiki/localize\">Help translate this content!</a>"
},
"disabledMessage": {
"en-GB": "Scripts are disabled. Showing fallback image.",
"zh-CN": "Scripts are disabled. Showing fallback image.",
"ja-JP": "Scripts are disabled. Showing fallback image."
},
"changelogTitle": {
"en-GB": "What's new?",
"zh-CN": "What's new?",
"ja-JP": "What's new?"
},
"changelogDescription": {
"en-GB": "This primer is a living document, and so depending on when you last look at it, there may be new content. Click the following link to expand this section to have a look at what got added, when.",
"zh-CN": "This primer is a living document, and so depending on when you last look at it, there may be new content. Click the following link to expand this section to have a look at what got added, when.",
"ja-JP": "This primer is a living document, and so depending on when you last look at it, there may be new content. Click the following link to expand this section to have a look at what got added, when."
}
}

View File

@@ -14,7 +14,8 @@
"url": "https://github.com/Pomax/bezierinfo/issues"
},
"scripts": {
"start": "run-s build",
"start": "run-s lint build",
"lint": "prettier ./tools --write",
"build": "node ./tools/build.js",
"test": "run-p server browser",
"server": "http-server -p 8000 --cors",

View File

@@ -1,3 +1,4 @@
import LocaleStrings from "./locale-strings.js";
import getAllChapterFiles from "./build/get-all-chapter-files.js";
import processLocale from "./build/process-locale.js";
import createIndexPages from "./build/create-index-page.js";
@@ -9,9 +10,9 @@ import createIndexPages from "./build/create-index-page.js";
*/
getAllChapterFiles().then((chapterFiles) => {
const languageCodes = Object.keys(chapterFiles);
languageCodes.forEach(async (locale) => {
const chapters = await processLocale(locale, chapterFiles);
createIndexPages(locale, chapters, languageCodes);
const localeStrings = new LocaleStrings(locale);
const chapters = await processLocale(locale, localeStrings, chapterFiles);
createIndexPages(locale, localeStrings, chapters);
});
});

View File

@@ -8,14 +8,18 @@ nunjucks.configure(".", { autoescape: false });
/**
* ...docs go here...
*/
export default async function convertMarkDown(chapter, locale, markdown) {
markdown = injectGraphicsFallback(chapter, locale, markdown);
export default async function convertMarkDown(
chapter,
localeStrings,
markdown
) {
markdown = injectGraphicsFallback(chapter, localeStrings, markdown);
const { data, latex } = extractLaTeX(markdown);
await Promise.all(
Object.keys(latex).map(async (key, pos) => {
const svg = await latexToSVG(latex[key], chapter, locale, pos + 1);
const svg = await latexToSVG(latex[key], chapter, localeStrings, pos + 1);
return (latex[key] = svg);
})
);

View File

@@ -3,28 +3,23 @@ import path from "path";
import prettier from "prettier";
import generateLangSwitcher from "./generate-lang-switcher.js";
import nunjucks from "nunjucks";
import localeStrings from "../../locale-strings.js";
import sectionOrder from "../../chapters/toc.js";
import changelog from "../../changelog.js";
const defaultLocale = localeStrings.defaultLocale;
nunjucks.configure(".", { autoescape: false });
/**
* ...docs go here...
*/
export default async function createIndexPages(locale, chapters, languages) {
let base = ``;
if (locale !== defaultLocale) {
base = `<base href="..">`;
}
const langSwitcher = generateLangSwitcher(locale, languages, defaultLocale);
export default async function createIndexPages(
locale,
localeStrings,
chapters
) {
const defaultLocale = localeStrings.getDefaultLocale();
const base = locale !== defaultLocale ? `<base href="..">` : ``;
const langSwitcher = generateLangSwitcher(localeStrings);
const toc = {};
const preface = `<section id="preface">${
chapters[sectionOrder[0]]
}</section>`;
@@ -40,10 +35,12 @@ export default async function createIndexPages(locale, chapters, languages) {
});
let changeLogHTML = [];
Object.keys(changelog).forEach(period => {
let changes = changelog[period].map(change => `<li>${change}</li>`).join(`\n`);
Object.keys(changelog).forEach((period) => {
let changes = changelog[period]
.map((change) => `<li>${change}</li>`)
.join(`\n`);
changeLogHTML.push(`<h2>${period}</h2><ul>${changes}</ul>`);
})
});
// Set up the templating context
const context = {
@@ -57,11 +54,7 @@ export default async function createIndexPages(locale, chapters, languages) {
};
// And inject all the relevant locale strings
Object.keys(localeStrings).forEach((key) => {
if (localeStrings[key][locale]) {
context[key] = localeStrings[key][locale];
}
});
localeStrings.extendContext(context);
const index = nunjucks.render(`index.template.html`, context);

View File

@@ -1,9 +1,8 @@
import localeStrings from "../../locale-strings.js";
export default function generateLangSwitcher(localeStrings) {
const defaultLocale = localeStrings.getDefaultLocale();
const defaultLocale = localeStrings.defaultLocale;
export default function generateLangSwitcher(currentLocale, allLocales) {
return allLocales
return localeStrings
.getAllLocaleCodes()
.map((locale) => {
let link;
if (locale === defaultLocale) {
@@ -11,7 +10,8 @@ export default function generateLangSwitcher(currentLocale, allLocales) {
} else {
link = `./${locale}/index.html`;
}
return `<li><a href="${link}">${localeStrings.localeName[locale]}</a></li>`;
let localeName = localeStrings.getLocaleName(locale);
return `<li><a href="${link}">${localeName}</a></li>`;
})
.join(`\n`);
}

View File

@@ -1,7 +1,7 @@
import fs from "fs-extra";
import path from "path";
import rewriteGraphicsElement from "./rewrite-graphics-element.js";
import localeStrings from "../../locale-strings.js";
import localeStrings from "../locale-strings.js";
const defaultLocale = localeStrings.defaultLocale;
@@ -29,7 +29,9 @@ export default async function generatePlaceHolders(locale, markdown) {
} while (pos !== -1);
const keys = Object.keys(elements);
const sourcePaths = keys.map(key => elements[key].match(/src="([^"]+)"/)[1]);
const sourcePaths = keys.map(
(key) => elements[key].match(/src="([^"]+)"/)[1]
);
await Promise.all(
sourcePaths.map(async (srcPath, i) => {
@@ -61,8 +63,9 @@ export default async function generatePlaceHolders(locale, markdown) {
// console.log(`Writing placeholder to ${filename}`);
fs.ensureDirSync(path.dirname(destPath));
fs.writeFileSync(filename, imageData);
} catch (e) {
console.error(e);
}
catch (e) { console.error(e); }
})
);
}

View File

@@ -11,7 +11,6 @@ const BASEDIR = path.join(__dirname, "..", "..");
*/
export default /* async */ function getAllChapterFiles() {
return new Promise((resolve, reject) => {
glob(path.join(BASEDIR, `chapters/**/content*md`), (err, files) => {
if (err) reject(err);

View File

@@ -1,15 +1,19 @@
export default function cleanUp(latex) {
// strip any \[ and \], which is a block-level LaTeX markup indicator for MathJax:
latex = latex.replace(/^'/,'').replace(/'$/,'').replace('\\[','').replace('\\]','');
latex = latex
.replace(/^'/, "")
.replace(/'$/, "")
.replace("\\[", "")
.replace("\\]", "");
// wrap some known functor words in italics markup
['Bézier'].forEach(term => {
latex = latex.replace(new RegExp(term, 'g'), '\\textit{' + term + '}');
["Bézier"].forEach((term) => {
latex = latex.replace(new RegExp(term, "g"), "\\textit{" + term + "}");
});
// also unindent the LaTeX.
var indent = false;
var lines = latex.split('\n').filter(line => !!line.trim());
var lines = latex.split("\n").filter((line) => !!line.trim());
var clean = function (line, idx) {
if (!indent) {
var matched = line.match(/^(\s+)/);
@@ -18,10 +22,10 @@ export default function cleanUp(latex) {
}
}
if (indent) {
lines[idx] = line.replace(indent,'').trim();
lines[idx] = line.replace(indent, "").trim();
}
}
lines.forEach(clean);
latex = lines.join('\n');
return latex;
};
lines.forEach(clean);
latex = lines.join("\n");
return latex;
}

View File

@@ -47,7 +47,7 @@ export default async function latexToSVG(latex, chapter, locale, block) {
"\\usepackage{xeCJK}",
"\\xeCJKsetup{CJKmath=true}",
"\\setCJKmainfont{gbsn00lp.ttf}",
]
];
}
// The same goes for Japanese, although we obviously want a different
@@ -69,13 +69,16 @@ export default async function latexToSVG(latex, chapter, locale, block) {
`\\usepackage{color}`,
`\\usepackage{amsmath}`,
`\\usepackage{unicode-math}`,
].concat(fonts).concat([
]
.concat(fonts)
.concat([
`\\begin{document}`,
`\\[`,
cleanUp(latex),
`\\]`,
`\\end{document}`,
]).join(`\n`)
])
.join(`\n`)
);
// Finally: run the conversion

View File

@@ -1,9 +1,13 @@
import localeStrings from "../../../locale-strings.js";
/**
* ...docs go here...
*/
export default function injectGraphicsFallback(chapter, locale, markdown) {
export default function injectGraphicsFallback(
chapter,
localeStrings,
markdown
) {
const translate = localeStrings.translate;
let pos = -1,
data = markdown,
startmark = `<graphics-element`,
@@ -22,7 +26,7 @@ export default function injectGraphicsFallback(chapter, locale, markdown) {
return `width="${width}" height="${height}" src="${src}">
<fallback-image>
<img width="${width}px" height="${height}px" src="${img}" loading="lazy">
${localeStrings.disabledMessage[locale]}
${translate`disabledMessage`}
</fallback-image>`;
}
);

View File

@@ -4,12 +4,10 @@ import convertMarkDown from "./convert-markdown.js";
import generatePlaceHolders from "./generate-placeholders.js";
import nunjucks from "nunjucks";
import toc from "../../chapters/toc.js";
import localeStrings from "../../locale-strings.js";
const moduleURL = new URL(import.meta.url);
const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
const defaultLocale = localeStrings.defaultLocale;
const sectionList = toc.map((v) =>
path.posix.join(
__dirname.split(path.sep).join(path.posix.sep),
@@ -25,7 +23,14 @@ nunjucks.configure(".", { autoescape: false });
/**
* ...docs go here...
*/
export default async function processLocale(locale, chapterFiles) {
export default async function processLocale(
locale,
localeStrings,
chapterFiles
) {
const defaultLocale = localeStrings.getDefaultLocale();
const translate = localeStrings.translate;
const localeFiles = chapterFiles[locale];
let localized = 0;
let missing = 0;
@@ -53,10 +58,7 @@ export default async function processLocale(locale, chapterFiles) {
localeFiles.map(async (file) => {
const chapter = file.match(/chapters\/([^/]+)\/content./)[1];
const markdown = fs.readFileSync(file).toString("utf8");
const replaced = nunjucks.renderString(markdown, {
disableMessage: `<span>${localeStrings.disabledMessage[locale]}</span>`,
});
const converted = await convertMarkDown(chapter, locale, replaced);
const converted = await convertMarkDown(chapter, localeStrings, markdown);
chapters[chapter] = converted;
generatePlaceHolders(locale, converted); // ← this is super fancy functionality.
})

View File

@@ -3,12 +3,12 @@ import performCodeSurgery from "../../lib/custom-element/lib/perform-code-surger
import prettier from "prettier";
export default function rewriteGraphicsElement(code, width, height) {
const split = splitCodeSections(code);
const globalCode = split.quasiGlobal;
const classCode = performCodeSurgery(split.classCode);
return prettier.format(`
return prettier.format(
`
import CanvasBuilder from 'canvas';
import { GraphicsAPI, Bezier, Vector } from "../../lib/custom-element/api/graphics-api.js";
@@ -33,5 +33,7 @@ export default function rewriteGraphicsElement(code, width, height) {
const canvas = example.canvas;
export { canvas };
`, { parser: `babel` });
};
`,
{ parser: `babel` }
);
}

110
tools/locale-strings.js Normal file
View File

@@ -0,0 +1,110 @@
const defaultLocale = "en-GB";
const localeStringData = {
title: {
"en-GB": "A Primer on Bézier Curves",
"ja-JP": "ベジェ曲線入門",
"zh-CN": "贝塞尔曲线底漆",
},
subtitle: {
"en-GB":
"A free, online book for when you really need to know how to do Bézier things.",
},
description: {
"en-GB":
"A detailed explanation of Bézier curves, and how to do the many things that we commonly want to do with them.",
},
tocLabel: {
"en-GB": "Table of Contents",
"ja-JP": "目次",
"zh-CN": "目录",
},
localeName: {
"en-GB": "English",
"zh-CN": "中文",
"ja-JP": "日本語",
},
langSwitchLabel: {
"en-GB": "Read this in your own language:",
},
langHelpLabel: {
"en-GB":
'Don\'t see your language listed? <a href="https://github.com/Pomax/BezierInfo-2/wiki/localize">Help translate this content!</a>',
},
disabledMessage: {
"en-GB": "Scripts are disabled. Showing fallback image.",
},
changelogTitle: {
"en-GB": "What's new?",
},
changelogDescription: {
"en-GB":
"This primer is a living document, and so depending on when you last look at it, there may be new content. Click the following link to expand this section to have a look at what got added, when.",
},
};
class LocaleStrings {
constructor(locale) {
this.currentLocale = locale;
const strings = (this.strings = {});
Object.keys(localeStringData).forEach((id) => {
const map = localeStringData[id];
if (typeof map !== "object") return;
const value = map[locale] ?? map[defaultLocale];
if (!value) throw new Error(`unknown locale string id "${id}".`);
strings[id] = value;
// temporary bug catcher:
Object.defineProperty(this, id, {
get: () => {
throw new Error(`cannot get localestring ${id} via property access`);
},
});
});
}
// templating tags:
get translate() {
return this.get.bind(this);
}
// functions:
get(id) {
return this.strings[id];
}
getDefaultLocale() {
return defaultLocale;
}
getCurrentLocale() {
return this.currentLocale;
}
getLocaleName(locale) {
const name = localeStringData.localeName[locale];
if (!name) {
throw new Error(`Unknown locale "${locale}"`);
}
return name;
}
getAllLocaleCodes() {
return Object.keys(localeStringData.title);
}
extendContext(context) {
const strings = this.strings;
Object.keys(strings).forEach((key) => {
if (context[key]) {
throw new Error(`Context already has key "${key}"!`);
}
context[key] = strings[key];
});
}
}
export default LocaleStrings;