@8bitscript/i18n 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/package.json +29 -0
- package/src/index.8bs +29 -0
- package/src/index.de.8bs +6 -0
- package/src/index.fr.8bs +7 -0
- package/src/index.it.8bs +6 -0
- package/src/index.nl.8bs +6 -0
- package/src/index.pt-br.8bs +6 -0
- package/src/number.8bs +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 8BitScript contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @8bitscript/i18n
|
|
2
|
+
|
|
3
|
+
What the build's locale is like, decided at compile time — the same on
|
|
4
|
+
every target, VIC-20, C64, PET, C128, Atari 8-bit, NES, Commander X16,
|
|
5
|
+
MEGA65, and web. Two import paths: the facts, and a number printed with
|
|
6
|
+
them.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add @8bitscript/i18n
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
import { Locale } from "@8bitscript/i18n"; // the facts: two ASCII codes
|
|
14
|
+
import { number } from "@8bitscript/i18n/number"; // a grouped number, through @8bitscript/text
|
|
15
|
+
|
|
16
|
+
text.putChar(cell, Locale.DECIMAL); // '.' — or ',' in a German build
|
|
17
|
+
number.print(cell, score, 6); // "12,345" — or "12.345"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Name | What it is |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `Locale.DECIMAL` | The character between a number's whole and fractional parts, as the ASCII code `text.putChar` takes: `.` (46), or `,` (44) |
|
|
23
|
+
| `Locale.GROUP` | The character between groups of three digits: `,` (44), `.` (46), or a space (32) |
|
|
24
|
+
| `number.print(cell, value, width)` | `value` as decimal digits with `Locale.GROUP` every three from the right, right-aligned in `width` cells from `cell`, blank ahead of it. Six cells hold the widest `usmallint`, `65,535`; a narrower field prints the whole number from `cell` rather than cutting it |
|
|
25
|
+
|
|
26
|
+
## How a locale gets in
|
|
27
|
+
|
|
28
|
+
A locale is a build input, never a run-time one: `8bs build --locale de`,
|
|
29
|
+
a target's `locale`, or a `release` entry's (see
|
|
30
|
+
[Project config](../../docs/config.md#one-binary-per-locale)). This
|
|
31
|
+
package is one file per locale — `src/index.8bs` for a build that names
|
|
32
|
+
none, `index.de.8bs`, `index.fr.8bs`, `index.it.8bs`, `index.nl.8bs`,
|
|
33
|
+
`index.pt-br.8bs` beside it — and the compiler's resolver picks the file
|
|
34
|
+
by the same twin rule that takes a program's own `strings.de.8bs` over its
|
|
35
|
+
`strings.8bs`. Nothing in a program names a locale; nothing is looked up
|
|
36
|
+
while it runs; a program that reads neither name links none of this.
|
|
37
|
+
|
|
38
|
+
A locale with no file here silently reads the plain one — `--locale sv`
|
|
39
|
+
gets `.` and `,`. Add a twin (five lines; cite the CLDR entry, as each of
|
|
40
|
+
the shipped ones does) rather than branch on `#locale("sv")` in a program.
|
|
41
|
+
Spanish is deliberately not shipped yet: CLDR gives `es` a
|
|
42
|
+
`minimumGroupingDigits` of 2, so a four-digit number is written `1234`
|
|
43
|
+
and only `12.345` is grouped — a rule `number.print` does not express,
|
|
44
|
+
and a twin that said `.` alone would print `1.234`. Check the key in
|
|
45
|
+
CLDR's `numbers.json` for `es` before adding it.
|
|
46
|
+
|
|
47
|
+
## What is here, and what is not
|
|
48
|
+
|
|
49
|
+
The two facts are the ones a program on a character grid can act on.
|
|
50
|
+
Every value is a literal in the portable character set — `,` `.` and the
|
|
51
|
+
space are on every machine, including the NES's own font — so a build
|
|
52
|
+
never reaches for a glyph its target cannot draw. A read of one folds to
|
|
53
|
+
the literal: a PET, VIC-20 or C64 program that writes `Locale.GROUP` is
|
|
54
|
+
byte-identical to one that writes `44` (a test builds both and compares
|
|
55
|
+
the images). Nothing here is a
|
|
56
|
+
string: a program's strings are its own, in a `strings.8bs` and that
|
|
57
|
+
file's locale twins, where the compiler can see their lengths. Nothing
|
|
58
|
+
here names an accented letter or a currency sign, because no target can
|
|
59
|
+
show one; a language written in them is transliterated in the program's
|
|
60
|
+
strings file (`DRUECKEN`), the way a typewriter without the keys spelled
|
|
61
|
+
it, and that is a decision the program's author makes line by line.
|
|
62
|
+
|
|
63
|
+
`number.print` is code — the place-value loop every machine's
|
|
64
|
+
`text.printNumber` uses to find digits without a divide, plus the
|
|
65
|
+
separator — and lives at its own subpath so that a program printing a
|
|
66
|
+
zero-padded HUD field (`text.printNumber`, or a `${score:5}` template)
|
|
67
|
+
carries none of it. What it costs, measured 2026-09-17 as one number
|
|
68
|
+
printed this way instead of with `text.printNumber`, in a program that
|
|
69
|
+
owns its machine: +209 bytes and +11 of RAM on the PET, +202 and +11 on
|
|
70
|
+
the unexpanded VIC-20, +194 and +9 on the C64. The two are for different
|
|
71
|
+
numbers: `printNumber` is the counter that must never shift columns,
|
|
72
|
+
this is the total the player reads.
|
|
73
|
+
|
|
74
|
+
Tests: `pnpm --filter @8bitscript/i18n test` — resolution per locale on
|
|
75
|
+
every machine, a clean link on all nine with and without a locale, the
|
|
76
|
+
PET and VIC-20 images built for real (the fact byte-identical to its
|
|
77
|
+
literal; `number.print`'s frame inside their zero page), and
|
|
78
|
+
`number.print` built and run for the web, its cells read back, in
|
|
79
|
+
English and in German.
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@8bitscript/i18n",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "What a locale is like, decided at compile time: the bare import is the build's locale's own facts — its decimal and grouping separators — as a twin file per locale inside the package, so a program reads Locale.GROUP and the build picks the file; ./number prints a grouped number through @8bitscript/text with them. Costs nothing where it is not read.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/8BitScript/8bitscript.git",
|
|
9
|
+
"directory": "packages/i18n"
|
|
10
|
+
},
|
|
11
|
+
"8bitscript": {
|
|
12
|
+
"entry": "./src/index.8bs",
|
|
13
|
+
"exports": {
|
|
14
|
+
"./number": "./src/number.8bs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@8bitscript/text": "workspace:*"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.8bs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @8bitscript/i18n — what the build's locale is like, as compile-time
|
|
2
|
+
// facts. This file is the one a build with no locale named reads: the
|
|
3
|
+
// program's source language, which for every program so far has been
|
|
4
|
+
// English, so the numbers below are English's. Beside it, index.de.8bs
|
|
5
|
+
// and the rest are one locale each, and `8bs build --locale de` (or a
|
|
6
|
+
// target's `locale`, or a `release` entry's) takes that file instead —
|
|
7
|
+
// the resolver's locale twin rule, the same one that takes a program's
|
|
8
|
+
// own strings.de.8bs over its strings.8bs (docs/config.md, "One binary
|
|
9
|
+
// per locale"). A locale with no file here reads this one, silently:
|
|
10
|
+
// add the twin (five lines) rather than branch on #locale("...").
|
|
11
|
+
//
|
|
12
|
+
// Every value is a literal in a namespace, so a read of Locale.GROUP is
|
|
13
|
+
// one byte in the image and a branch on it folds; a program that never
|
|
14
|
+
// reads it links none of this. What is here is what a program on a
|
|
15
|
+
// character grid can act on: the two separators a number is written
|
|
16
|
+
// with, as the ASCII codes @8bitscript/text's putChar takes. Both are
|
|
17
|
+
// in the portable character set on every machine (`,` and `.`; a space
|
|
18
|
+
// where a language groups with one). What is not here is anything the
|
|
19
|
+
// machines cannot show — an accented letter, a currency sign — and
|
|
20
|
+
// anything that is a string: strings are the program's own, in its
|
|
21
|
+
// strings.8bs and that file's locale twins.
|
|
22
|
+
//
|
|
23
|
+
// Sources: CLDR's number symbols for each locale (unicode.org/cldr);
|
|
24
|
+
// each twin cites the entry it was read from.
|
|
25
|
+
|
|
26
|
+
export namespace Locale {
|
|
27
|
+
const DECIMAL: utinyint = 46; // '.' — 3.5
|
|
28
|
+
const GROUP: utinyint = 44; // ',' — 65,535
|
|
29
|
+
}
|
package/src/index.de.8bs
ADDED
package/src/index.fr.8bs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// French — index.8bs's twin for `--locale fr`. CLDR fr: decimal ",",
|
|
2
|
+
// group U+202F narrow no-break space (1 234,5); a character grid has one
|
|
3
|
+
// space, so it is 32.
|
|
4
|
+
export namespace Locale {
|
|
5
|
+
const DECIMAL: utinyint = 44; // ','
|
|
6
|
+
const GROUP: utinyint = 32; // ' '
|
|
7
|
+
}
|
package/src/index.it.8bs
ADDED
package/src/index.nl.8bs
ADDED
package/src/number.8bs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @8bitscript/i18n/number — a number the way the build's locale writes
|
|
2
|
+
// it, through @8bitscript/text: `number.print(cell, value, width)` writes
|
|
3
|
+
// `value` as decimal digits with Locale.GROUP between every three, from
|
|
4
|
+
// the right, right-aligned in a field of `width` cells from `cell`, the
|
|
5
|
+
// cells ahead of it blank — "1,234" in an English build and "1.234" in a
|
|
6
|
+
// German one, from the same call. The most a usmallint takes is six
|
|
7
|
+
// cells ("65,535"); a field narrower than its number is printed from
|
|
8
|
+
// `cell` anyway, so the number is never cut, and a caller that sizes the
|
|
9
|
+
// field at six is safe for every value.
|
|
10
|
+
//
|
|
11
|
+
// Opt-in, at its own subpath: unlike the facts at the bare import this is
|
|
12
|
+
// code — a loop over the place values, the way every machine's
|
|
13
|
+
// text.printNumber works out digits without a divide (the 6502 has none,
|
|
14
|
+
// and `value / 10` would link a 250-byte routine) — and a program that
|
|
15
|
+
// prints its numbers as `text.printNumber`'s zero-padded field, or in a
|
|
16
|
+
// template, pays nothing for it. text.printNumber is the field for a HUD
|
|
17
|
+
// counter that must not shift columns; this is the number for a total
|
|
18
|
+
// the player reads.
|
|
19
|
+
import { text } from "@8bitscript/text";
|
|
20
|
+
import { Locale } from "./index.8bs";
|
|
21
|
+
|
|
22
|
+
const PLACES: array<usmallint, 5> = [10000, 1000, 100, 10, 1];
|
|
23
|
+
|
|
24
|
+
// Written for a small zero-page frame — the 6502 backend keeps every
|
|
25
|
+
// local and parameter of the deepest call chain in zero page, so a local
|
|
26
|
+
// here is a byte of every program's tightest budget: a first cut with
|
|
27
|
+
// four locals for the digit count and the field needed 19 bytes, this
|
|
28
|
+
// one 15 (text.printNumber's own is 13). `cell` is the cursor, and
|
|
29
|
+
// `remaining` is the digit count, the number's width, and the countdown
|
|
30
|
+
// to the separator in turn. What the whole thing costs, measured
|
|
31
|
+
// 2026-09-17 as one number printed this way instead of with
|
|
32
|
+
// text.printNumber, in a program that owns its machine: +209 bytes and
|
|
33
|
+
// +11 of RAM on the PET, +202 and +11 on the unexpanded VIC-20, +194 and
|
|
34
|
+
// +9 on the C64 — @8bitscript/text's fill() and putChar() linked beside
|
|
35
|
+
// its place(), and the separator's own loop.
|
|
36
|
+
export namespace number {
|
|
37
|
+
function print(cell: usmallint, value: usmallint, width: utinyint): void {
|
|
38
|
+
// How many digits: the first place the value reaches, high to
|
|
39
|
+
// low — one digit for 0-9, so a zero prints as one "0".
|
|
40
|
+
let remaining: utinyint = 1;
|
|
41
|
+
let i: utinyint = 0;
|
|
42
|
+
while (i < 4) {
|
|
43
|
+
if (remaining == 1 && value >= PLACES[i]) {
|
|
44
|
+
remaining = 5 - i;
|
|
45
|
+
}
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
// Five digits at most, so at most one separator, and only when
|
|
49
|
+
// there is a fourth digit: the number's width is one more then.
|
|
50
|
+
i = remaining;
|
|
51
|
+
if (remaining > 3) {
|
|
52
|
+
i = remaining + 1;
|
|
53
|
+
}
|
|
54
|
+
if (width > i) {
|
|
55
|
+
text.fill(cell, width - i, 32);
|
|
56
|
+
cell = cell + (width - i);
|
|
57
|
+
}
|
|
58
|
+
i = 5 - remaining; // the first place printed
|
|
59
|
+
while (i < 5) {
|
|
60
|
+
let digit: utinyint = 48; // '0'
|
|
61
|
+
while (value >= PLACES[i]) {
|
|
62
|
+
value = value - PLACES[i];
|
|
63
|
+
digit++;
|
|
64
|
+
}
|
|
65
|
+
text.putChar(cell, digit);
|
|
66
|
+
cell++;
|
|
67
|
+
remaining--;
|
|
68
|
+
if (remaining == 3) {
|
|
69
|
+
text.putChar(cell, Locale.GROUP);
|
|
70
|
+
cell++;
|
|
71
|
+
}
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|