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() { onKeyDown() {
this.mark = false;
if (this.keyboard[`ArrowDown`]) { if (this.keyboard[`ArrowDown`]) {
this.step--; this.stepDown();
if (this.step < 10) this.step = 10;
} }
if (this.keyboard[`ArrowUp`]) { if (this.keyboard[`ArrowUp`]) {
this.step++; this.stepUp();
if (this.step > 90) this.step = 90;
} }
redraw(); 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() { onMouseMove() {
this.curve.update(); 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(); redraw();
} }

View File

@@ -236,7 +236,7 @@ class GraphicsAPI extends BaseAPI {
* Add a plain point to the current shape * Add a plain point to the current shape
*/ */
vertex(x, y) { vertex(x, y) {
this.currentShape.vertex({ x, y}); this.currentShape.vertex({ x, y });
} }
/** /**
@@ -244,7 +244,7 @@ class GraphicsAPI extends BaseAPI {
*/ */
end() { end() {
this.ctx.beginPath(); this.ctx.beginPath();
let {x, y} = this.currentShape.first; let { x, y } = this.currentShape.first;
this.ctx.moveTo(x, y); this.ctx.moveTo(x, y);
this.currentShape.segments.forEach((s) => this.currentShape.segments.forEach((s) =>
this[`draw${s.type}`](this.ctx, s.points, s.factor) this[`draw${s.type}`](this.ctx, s.points, s.factor)
@@ -279,6 +279,58 @@ class GraphicsAPI extends BaseAPI {
this.line({ x: 0, y }, { x: this.width, y }); 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 }; 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" "url": "https://github.com/Pomax/bezierinfo/issues"
}, },
"scripts": { "scripts": {
"start": "run-s build", "start": "run-s lint build",
"lint": "prettier ./tools --write",
"build": "node ./tools/build.js", "build": "node ./tools/build.js",
"test": "run-p server browser", "test": "run-p server browser",
"server": "http-server -p 8000 --cors", "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 getAllChapterFiles from "./build/get-all-chapter-files.js";
import processLocale from "./build/process-locale.js"; import processLocale from "./build/process-locale.js";
import createIndexPages from "./build/create-index-page.js"; import createIndexPages from "./build/create-index-page.js";
@@ -9,9 +10,9 @@ import createIndexPages from "./build/create-index-page.js";
*/ */
getAllChapterFiles().then((chapterFiles) => { getAllChapterFiles().then((chapterFiles) => {
const languageCodes = Object.keys(chapterFiles); const languageCodes = Object.keys(chapterFiles);
languageCodes.forEach(async (locale) => { languageCodes.forEach(async (locale) => {
const chapters = await processLocale(locale, chapterFiles); const localeStrings = new LocaleStrings(locale);
createIndexPages(locale, chapters, languageCodes); const chapters = await processLocale(locale, localeStrings, chapterFiles);
createIndexPages(locale, localeStrings, chapters);
}); });
}); });

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
import fs from "fs-extra"; import fs from "fs-extra";
import path from "path"; import path from "path";
import rewriteGraphicsElement from "./rewrite-graphics-element.js"; import rewriteGraphicsElement from "./rewrite-graphics-element.js";
import localeStrings from "../../locale-strings.js"; import localeStrings from "../locale-strings.js";
const defaultLocale = localeStrings.defaultLocale; const defaultLocale = localeStrings.defaultLocale;
const moduleURL = new URL(import.meta.url); const moduleURL = new URL(import.meta.url);
const __dirname = path.dirname(moduleURL.href.replace(`file:///`,``)); const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
export default async function generatePlaceHolders(locale, markdown) { export default async function generatePlaceHolders(locale, markdown) {
if (locale !== defaultLocale) return; if (locale !== defaultLocale) return;
@@ -29,10 +29,12 @@ export default async function generatePlaceHolders(locale, markdown) {
} while (pos !== -1); } while (pos !== -1);
const keys = Object.keys(elements); 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( await Promise.all(
sourcePaths.map(async(srcPath, i) => { sourcePaths.map(async (srcPath, i) => {
try { try {
// Get the sketch code // Get the sketch code
const sourcePath = path.join(__dirname, "..", "..", srcPath); const sourcePath = path.join(__dirname, "..", "..", srcPath);
@@ -61,8 +63,9 @@ export default async function generatePlaceHolders(locale, markdown) {
// console.log(`Writing placeholder to ${filename}`); // console.log(`Writing placeholder to ${filename}`);
fs.ensureDirSync(path.dirname(destPath)); fs.ensureDirSync(path.dirname(destPath));
fs.writeFileSync(filename, imageData); fs.writeFileSync(filename, imageData);
} catch (e) {
console.error(e);
} }
catch (e) { console.error(e); }
}) })
); );
} }

View File

@@ -3,7 +3,7 @@ import path from "path";
// make sure we know what our base location is // make sure we know what our base location is
const moduleURL = new URL(import.meta.url); const moduleURL = new URL(import.meta.url);
const __dirname = path.dirname(moduleURL.href.replace(`file:///`,``)); const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
const BASEDIR = path.join(__dirname, "..", ".."); const BASEDIR = path.join(__dirname, "..", "..");
/** /**
@@ -11,7 +11,6 @@ const BASEDIR = path.join(__dirname, "..", "..");
*/ */
export default /* async */ function getAllChapterFiles() { export default /* async */ function getAllChapterFiles() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
glob(path.join(BASEDIR, `chapters/**/content*md`), (err, files) => { glob(path.join(BASEDIR, `chapters/**/content*md`), (err, files) => {
if (err) reject(err); if (err) reject(err);

View File

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

View File

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

View File

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

View File

@@ -4,12 +4,10 @@ import convertMarkDown from "./convert-markdown.js";
import generatePlaceHolders from "./generate-placeholders.js"; import generatePlaceHolders from "./generate-placeholders.js";
import nunjucks from "nunjucks"; import nunjucks from "nunjucks";
import toc from "../../chapters/toc.js"; import toc from "../../chapters/toc.js";
import localeStrings from "../../locale-strings.js";
const moduleURL = new URL(import.meta.url); const moduleURL = new URL(import.meta.url);
const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``)); const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
const defaultLocale = localeStrings.defaultLocale;
const sectionList = toc.map((v) => const sectionList = toc.map((v) =>
path.posix.join( path.posix.join(
__dirname.split(path.sep).join(path.posix.sep), __dirname.split(path.sep).join(path.posix.sep),
@@ -25,7 +23,14 @@ nunjucks.configure(".", { autoescape: false });
/** /**
* ...docs go here... * ...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]; const localeFiles = chapterFiles[locale];
let localized = 0; let localized = 0;
let missing = 0; let missing = 0;
@@ -53,10 +58,7 @@ export default async function processLocale(locale, chapterFiles) {
localeFiles.map(async (file) => { localeFiles.map(async (file) => {
const chapter = file.match(/chapters\/([^/]+)\/content./)[1]; const chapter = file.match(/chapters\/([^/]+)\/content./)[1];
const markdown = fs.readFileSync(file).toString("utf8"); const markdown = fs.readFileSync(file).toString("utf8");
const replaced = nunjucks.renderString(markdown, { const converted = await convertMarkDown(chapter, localeStrings, markdown);
disableMessage: `<span>${localeStrings.disabledMessage[locale]}</span>`,
});
const converted = await convertMarkDown(chapter, locale, replaced);
chapters[chapter] = converted; chapters[chapter] = converted;
generatePlaceHolders(locale, converted); // ← this is super fancy functionality. 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"; import prettier from "prettier";
export default function rewriteGraphicsElement(code, width, height) { export default function rewriteGraphicsElement(code, width, height) {
const split = splitCodeSections(code);
const globalCode = split.quasiGlobal;
const classCode = performCodeSurgery(split.classCode);
const split = splitCodeSections(code); return prettier.format(
const globalCode = split.quasiGlobal; `
const classCode = performCodeSurgery(split.classCode);
return prettier.format(`
import CanvasBuilder from 'canvas'; import CanvasBuilder from 'canvas';
import { GraphicsAPI, Bezier, Vector } from "../../lib/custom-element/api/graphics-api.js"; 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; const canvas = example.canvas;
export { 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;