Initial commit (WIP WIP WIP)

This commit is contained in:
Mikko Ahlroth 2024-05-09 18:49:58 +03:00
commit 68e0be1741
20 changed files with 1993 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# kielet
[![Package Version](https://img.shields.io/hexpm/v/kielet)](https://hex.pm/packages/kielet)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/kielet/)
```sh
gleam add kielet
```
```gleam
import kielet
pub fn main() {
// TODO: An example of the project in use
}
```
Further documentation can be found at <https://hexdocs.pm/kielet>.
## Development
```sh
gleam run # Run the project
gleam test # Run the tests
gleam shell # Run an Erlang shell
```

1098
expo_plural_forms_parser.erl Normal file

File diff suppressed because it is too large Load diff

BIN
fi.mo Normal file

Binary file not shown.

34
fi.po Normal file
View file

@ -0,0 +1,34 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: mikko@ahlroth.fi\n"
"POT-Creation-Date: 2024-05-09 14:33+0300\n"
"PO-Revision-Date: 2024-05-09 14:36+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.4.2\n"
#: src/kielet/tester.gleam:6
msgid "Press to activate"
msgstr "Paina aktivoidaksesi"
#: src/kielet/tester.gleam:9
#, c-format
msgid "Press to activate %s button"
msgid_plural "Press to activate %s buttons"
msgstr[0] "Paina aktivoidaksesi %s nappi"
msgstr[1] "Paina aktivoidaksesi %s nappia"
#~ msgid "foo"
#~ msgstr "Juu"

21
gleam.toml Normal file
View file

@ -0,0 +1,21 @@
name = "kielet"
version = "1.0.0"
target = "javascript"
# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
simplifile = ">= 1.7.0 and < 2.0.0"

14
manifest.toml Normal file
View file

@ -0,0 +1,14 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
]
[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
simplifile = { version = ">= 1.7.0 and < 2.0.0" }

22
messages.po Normal file
View file

@ -0,0 +1,22 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-01 09:06+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/kielet/tester.gleam:5
msgid "foo"
msgstr ""

30
messages.pot Normal file
View file

@ -0,0 +1,30 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Mikko Ahlroth
# This file is distributed under the same license as the Kielet package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Kielet 1.2.3\n"
"Report-Msgid-Bugs-To: mikko@ahlroth.fi\n"
"POT-Creation-Date: 2024-05-09 18:44+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/kielet/tester.gleam:15
msgid "Press to activate"
msgstr ""
#: src/kielet/tester.gleam:18
#, c-format
msgid "Press to activate %s button"
msgid_plural "Press to activate %s buttons"
msgstr[0] ""
msgstr[1] ""

20
src/kielet.gleam Normal file
View file

@ -0,0 +1,20 @@
import gleam/io
import kielet/database.{type Database}
pub fn main() {
io.println("Hello from kielet!")
}
pub fn gettext(db: Database, msgid: String, language_code: String) -> String {
database.translate_singular(db, msgid, language_code)
}
pub fn ngettext(
db: Database,
singular: String,
plural: String,
count: Int,
language_code: String,
) -> String {
database.translate_plural(db, singular, plural, count, language_code)
}

49
src/kielet/database.gleam Normal file
View file

@ -0,0 +1,49 @@
import gleam/dict.{type Dict}
import kielet/language.{type Language}
pub opaque type Database {
Database(languages: Dict(String, Language))
}
pub fn new() {
Database(languages: dict.new())
}
pub fn add_language(db: Database, lang: Language) {
Database(languages: dict.insert(db.languages, language.get_code(lang), lang))
}
pub fn translate_singular(db: Database, msgid: String, language_code: String) {
case dict.get(db.languages, language_code) {
Ok(lang) ->
case language.get_singular_translation(lang, msgid) {
Ok(translation) -> translation
Error(_) -> msgid
}
Error(_) -> msgid
}
}
pub fn translate_plural(
db: Database,
msgid: String,
plural: String,
count: Int,
language_code: String,
) {
let translations = case dict.get(db.languages, language_code) {
Ok(lang) ->
case language.get_plural_translations(lang, msgid) {
Ok(translations) -> translations
Error(_) -> [msgid, plural]
}
Error(_) -> [msgid, plural]
}
let assert [singular, plural] = translations
case count {
1 -> singular
_ -> plural
}
}

52
src/kielet/language.gleam Normal file
View file

@ -0,0 +1,52 @@
import gleam/dict
import gleam/result
import kielet/mo
pub type LanguageError {
MoParseError(err: mo.ParseError)
}
pub type TranslateError {
MsgIsSingular(String)
MsgIsPlural(String)
MsgNotFound(String)
}
pub opaque type Language {
Language(code: String, translations: mo.Translations)
}
pub fn load(code: String, mo_file: BitArray) {
use mo <- result.try(
mo.parse(mo_file)
|> result.map_error(MoParseError),
)
Ok(Language(code, mo.translations))
}
pub fn get_code(lang: Language) {
lang.code
}
pub fn get_singular_translation(lang: Language, msgid: String) {
case dict.get(lang.translations, msgid) {
Ok(mostring) ->
case mostring {
mo.Singular(content: c, ..) -> Ok(c)
_ -> Error(MsgIsPlural(msgid))
}
_ -> Error(MsgNotFound(msgid))
}
}
pub fn get_plural_translations(lang: Language, msgid: String) {
case dict.get(lang.translations, msgid) {
Ok(mostring) ->
case mostring {
mo.Plural(content: c, ..) -> Ok(c)
_ -> Error(MsgIsSingular(msgid))
}
_ -> Error(MsgNotFound(msgid))
}
}

253
src/kielet/mo.gleam Normal file
View file

@ -0,0 +1,253 @@
import gleam/bit_array
import gleam/bool
import gleam/dict.{type Dict}
import gleam/int
import gleam/list
import gleam/result
import gleam/string
const max_supported_major = 0
const eot = ""
const nul = "\u{0}"
pub type MoString {
Singular(context: String, content: String)
Plural(context: String, content: List(String))
}
pub type Translations =
Dict(String, MoString)
pub type Endianness {
BigEndian
LittleEndian
}
pub type ParseError {
MagicNumberNotFound
MalformedHeader
UnknownRevision(Revision)
OffsetPastEnd(Int)
MalformedOffsetTableEntry(BitArray)
StringNotUTF8(BitArray)
}
pub type Revision {
Revision(major: Int, minor: Int)
}
pub type Header {
Header(
revision: Revision,
string_count: Int,
og_table_offset: Int,
trans_table_offset: Int,
ht_size: Int,
ht_offset: Int,
)
}
pub type Mo {
Mo(endianness: Endianness, header: Header, translations: Translations)
}
pub fn parse(mo: BitArray) {
use #(endianness, rest) <- result.try(parse_magic(mo))
use header <- result.try(parse_header(endianness, rest))
use <- bool.guard(
header.revision.major > max_supported_major,
Error(UnknownRevision(header.revision)),
)
use <- bool.guard(
header.string_count == 0,
Ok(Mo(endianness: endianness, header: header, translations: dict.new())),
)
let total_size = bit_array.byte_size(mo)
use <- bool.guard(
header.og_table_offset >= total_size,
Error(OffsetPastEnd(header.og_table_offset)),
)
use <- bool.guard(
header.trans_table_offset >= total_size,
Error(OffsetPastEnd(header.trans_table_offset)),
)
use <- bool.guard(
header.ht_offset >= total_size,
Error(OffsetPastEnd(header.ht_offset)),
)
use translations <- result.try(parse_translations(endianness, header, mo))
Ok(Mo(endianness: endianness, header: header, translations: translations))
}
fn parse_magic(body: BitArray) {
case body {
<<0xde120495:size(32), rest:bytes>> -> Ok(#(LittleEndian, rest))
<<0x950412de:size(32), rest:bytes>> -> Ok(#(BigEndian, rest))
_ -> Error(MagicNumberNotFound)
}
}
fn parse_header(endianness: Endianness, body: BitArray) {
case endianness {
LittleEndian -> {
case body {
<<
major_bytes:bytes-size(2),
minor_bytes:bytes-size(2),
string_count_bytes:bytes-size(4),
og_table_offset_bytes:bytes-size(4),
trans_table_offset_bytes:bytes-size(4),
ht_size_bytes:bytes-size(4),
ht_offset_bytes:bytes-size(4),
_rest:bytes,
>> -> {
let assert Ok(major) = le_int_8(major_bytes)
let assert Ok(minor) = le_int_8(minor_bytes)
let assert Ok(string_count) = le_int_32(string_count_bytes)
let assert Ok(og_table_offset) = le_int_32(og_table_offset_bytes)
let assert Ok(trans_table_offset) =
le_int_32(trans_table_offset_bytes)
let assert Ok(ht_size) = le_int_32(ht_size_bytes)
let assert Ok(ht_offset) = le_int_32(ht_offset_bytes)
Ok(Header(
Revision(major, minor),
string_count,
og_table_offset,
trans_table_offset,
ht_size,
ht_offset,
))
}
_ -> Error(MalformedHeader)
}
}
BigEndian -> panic
}
}
fn parse_translations(endianness: Endianness, header: Header, mo: BitArray) {
let strings = list.range(0, header.string_count - 1)
use translations <- result.try(
list.try_fold(strings, dict.new(), fn(translations, i) {
let new_offset = i * 8
let og_offset = header.og_table_offset + new_offset
let trans_offset = header.trans_table_offset + new_offset
use #(og, translation) <- result.try(parse_translation(
endianness,
mo,
og_offset,
trans_offset,
))
let key = case og {
Singular(content: c, ..) -> c
Plural(content: [c, ..], ..) -> c
Plural(..) -> panic as "Got plural form with zero entries"
}
Ok(dict.insert(translations, key, translation))
}),
)
Ok(translations)
}
fn parse_translation(
endianness: Endianness,
mo: BitArray,
og_offset: Int,
trans_offset: Int,
) {
use #(og_str_length, og_str_offset) <- result.try(parse_offset_table_entry(
endianness,
mo,
og_offset,
))
use #(trans_str_length, trans_str_offset) <- result.try(
parse_offset_table_entry(endianness, mo, trans_offset),
)
use og_string <- result.try(parse_mo_string(mo, og_str_length, og_str_offset))
use trans_string <- result.try(parse_mo_string(
mo,
trans_str_length,
trans_str_offset,
))
Ok(#(og_string, trans_string))
}
fn parse_offset_table_entry(endianness: Endianness, mo: BitArray, offset: Int) {
use data <- result.try(
bit_array.slice(mo, offset, 8)
|> result.replace_error(OffsetPastEnd(offset)),
)
case endianness {
LittleEndian ->
case data {
<<target_length:bytes-size(4), target_offset:bytes-size(4)>> -> {
let assert Ok(target_length) = le_int_32(target_length)
let assert Ok(target_offset) = le_int_32(target_offset)
Ok(#(target_length, target_offset))
}
_ -> Error(MalformedOffsetTableEntry(data))
}
BigEndian -> panic
}
}
fn parse_mo_string(mo: BitArray, length: Int, offset: Int) {
use data <- result.try(
bit_array.slice(mo, offset, length)
|> result.replace_error(OffsetPastEnd(offset)),
)
use str <- result.try(
bit_array.to_string(data)
|> result.replace_error(StringNotUTF8(data)),
)
let #(context, str) = case string.split_once(str, eot) {
Ok(#(c, s)) -> #(c, s)
Error(_) -> #("", str)
}
case string.split(str, nul) {
[_] -> Ok(Singular(context: context, content: str))
plurals -> Ok(Plural(context: context, content: plurals))
}
}
fn le_int_8(int8: BitArray) {
case int8 {
<<l:size(8), h:size(8)>> -> Ok(reconstruct_ui8(h, l))
_ -> Error(Nil)
}
}
fn le_int_32(int32: BitArray) {
case int32 {
<<ll:size(8), lh:size(8), hl:size(8), hh:size(8)>> ->
Ok(reconstruct_ui32(hh, hl, lh, ll))
_ -> Error(Nil)
}
}
fn reconstruct_ui8(h: Int, l: Int) {
int.bitwise_shift_left(h, 8) + l
}
fn reconstruct_ui32(hh: Int, hl: Int, lh: Int, ll: Int) {
int.bitwise_shift_left(hh, 8 * 3)
+ int.bitwise_shift_left(hl, 8 * 2)
+ int.bitwise_shift_left(lh, 8)
+ ll
}

View file

@ -0,0 +1,13 @@
Copyright 2015 Plataformatec Copyright 2020 Dashbit 2022 JOSHMARTIN GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,148 @@
import gleam/result
import kielet/plurals/syntax_error
import kielet/plurals/tokenizer
pub type ParseError {
SyntaxError(err: syntax_error.SyntaxError)
}
pub type PluralForms {
PluralForms(nplurals: Int, ast: Ast)
}
type BinOp {
Equal
NotEqual
GreaterThan
GreaterThanOrEqual
LowerThan
LowerThanOrEqual
Remainder
And
Or
}
pub opaque type Ast {
N
Integer(Int)
BinaryOperation(operator: BinOp, lvalue: Ast, rvalue: Ast)
If(condition: Ast, truthy: Ast, falsy: Ast)
Paren(Ast)
}
pub fn parse(rules: String) {
use tokens <- result.try(
tokenizer.tokenize(rules)
|> result.map_error(SyntaxError),
)
do_parse(tokens)
}
fn eval_ast(ast: Ast, input: Int) {
case ast {
N -> input
Integer(i) -> i
If(condition, truthy, falsy) -> {
let ast = case eval_ast(condition, input) {
1 -> truthy
_ -> falsy
}
eval_ast(ast, input)
}
Paren(content) -> eval_ast(content, input)
BinaryOperation(operator, lvalue, rvalue) -> {
let lvalue = eval_ast(lvalue, input)
let rvalue = eval_ast(rvalue, input)
case operator {
Equal -> bool_to_int(lvalue == rvalue)
NotEqual -> bool_to_int(lvalue != rvalue)
GreaterThan -> bool_to_int(lvalue > rvalue)
GreaterThanOrEqual -> bool_to_int(lvalue >= rvalue)
LowerThan -> bool_to_int(lvalue < rvalue)
LowerThanOrEqual -> bool_to_int(lvalue <= rvalue)
Remainder -> lvalue % rvalue
And -> bool_to_int(lvalue == 1 && rvalue == 1)
Or -> bool_to_int(lvalue == 1 || rvalue == 1)
}
}
}
}
fn bool_to_int(bool: Bool) {
case bool {
True -> 1
False -> 0
}
}
type Location {
NoLocation
Line(Int)
}
fn do_parse(tokens: List(tokenizer.Token)) {
yeccpars0(tokens, NoLocation, 0, [], [])
}
fn yeccpars0(
tokens: List(tokenizer.Token),
location: Location,
state: Int,
states: List(Int),
vstack: List(tokenizer.Token),
) {
yeccpars1(tokens, location, state, states, vstack)
}
fn yeccpars1(
tokens: List(tokenizer.Token),
location: Location,
state: Int,
states: List(Int),
vstack: List(tokenizer.Token),
) {
case tokens {
[token, ..rest] ->
yeccpars2(state, token, states, vstack, token, rest, location)
[] ->
case location {
NoLocation ->
yeccpars2(
state,
tokenizer.End,
states,
vstack,
#(tokenizer.End, 999_999),
[],
999_999,
)
Line(line) ->
yeccpars2(
state,
tokenizer.End,
states,
vstac,
#(tokenizer.End, line),
[],
line,
)
}
}
}
fn yeccpars1_2(state1, state, states, vstack, token0, tokens, location) {
case tokens {
[token, ..rest] ->
yeccpars2(
state,
token,
[state1, ..states],
[token0, ..vstack],
token,
rest,
location,
)
[] -> yeccpars2()
}
}

View file

@ -0,0 +1,6 @@
import gleam/option
/// Error returned when the syntax in a plural forms string is invalid.
pub type SyntaxError {
SyntaxError(line: Int, column: option.Option(Int), reason: String)
}

View file

@ -0,0 +1,140 @@
import gleam/int
import gleam/list
import gleam/option
import gleam/string
import kielet/plurals/syntax_error.{SyntaxError}
pub type Token {
Token(type_: TokenType, line: Int)
Int(value: Int, line: Int)
}
pub type TokenType {
N
NPlurals
Plural
Equals
NotEquals
GreaterThanOrEquals
LowerThanOrEquals
GreaterThan
LowerThan
Assignment
Ternary
TernaryElse
Remainder
Or
And
Semicolon
LParen
RParen
End
}
pub fn tokenize(str: String) {
do_tokenize(string.to_graphemes(str), [], 1, 1)
}
fn do_tokenize(str: List(String), acc: List(Token), line: Int, col: Int) {
case str {
[] -> Ok(list.reverse([Token(End, line), ..acc]))
["n", "p", "l", "u", "r", "a", "l", "s", ..rest] ->
do_tokenize(rest, [Token(NPlurals, line), ..acc], line, col + 8)
["p", "l", "u", "r", "a", "l", ..rest] ->
do_tokenize(rest, [Token(Plural, line), ..acc], line, col + 6)
["n", ..rest] -> do_tokenize(rest, [Token(N, line), ..acc], line, col + 1)
["\\", "\n", ..rest] -> do_tokenize(rest, acc, line + 1, 1)
["\n", ..rest] -> do_tokenize(rest, acc, line + 1, 1)
[" ", ..rest] -> do_tokenize(rest, acc, line, col + 1)
["=", "=", ..rest] ->
do_tokenize(rest, [Token(Equals, line), ..acc], line, col + 2)
["!", "=", ..rest] ->
do_tokenize(rest, [Token(NotEquals, line), ..acc], line, col + 2)
[">", "=", ..rest] ->
do_tokenize(
rest,
[Token(GreaterThanOrEquals, line), ..acc],
line,
col + 2,
)
["<", "=", ..rest] ->
do_tokenize(rest, [Token(LowerThanOrEquals, line), ..acc], line, col + 2)
[">", ..rest] ->
do_tokenize(rest, [Token(GreaterThan, line), ..acc], line, col + 1)
["<", ..rest] ->
do_tokenize(rest, [Token(LowerThan, line), ..acc], line, col + 1)
["=", ..rest] ->
do_tokenize(rest, [Token(Assignment, line), ..acc], line, col + 1)
["?", ..rest] ->
do_tokenize(rest, [Token(Ternary, line), ..acc], line, col + 1)
[":", ..rest] ->
do_tokenize(rest, [Token(TernaryElse, line), ..acc], line, col + 1)
["%", ..rest] ->
do_tokenize(rest, [Token(Remainder, line), ..acc], line, col + 1)
["|", "|", ..rest] ->
do_tokenize(rest, [Token(Or, line), ..acc], line, col + 2)
["&", "&", ..rest] ->
do_tokenize(rest, [Token(And, line), ..acc], line, col + 2)
[";", ..rest] ->
do_tokenize(rest, [Token(Semicolon, line), ..acc], line, col + 1)
[")", ..rest] ->
do_tokenize(rest, [Token(RParen, line), ..acc], line, col + 1)
["(", ..rest] ->
do_tokenize(rest, [Token(LParen, line), ..acc], line, col + 1)
[digit, ..rest] if digit == "0"
|| digit == "1"
|| digit == "2"
|| digit == "3"
|| digit == "4"
|| digit == "5"
|| digit == "6"
|| digit == "7"
|| digit == "8"
|| digit == "9" -> read_digits(rest, acc, line, col + 1, digit)
[grapheme, ..] ->
Error(SyntaxError(
reason: "Unexpected grapheme " <> grapheme,
line: line,
column: option.Some(col),
))
}
}
fn read_digits(
str: List(String),
acc: List(Token),
line: Int,
col: Int,
digit_acc: String,
) {
case str {
[digit, ..rest]
if digit == "0"
|| digit == "1"
|| digit == "2"
|| digit == "3"
|| digit == "4"
|| digit == "5"
|| digit == "6"
|| digit == "7"
|| digit == "8"
|| digit == "9"
-> read_digits(rest, acc, line, col + 1, digit_acc <> digit)
other ->
case int.parse(digit_acc) {
Ok(int) ->
do_tokenize(
other,
[Int(int, line), ..acc],
line,
col + string.length(digit_acc),
)
Error(_) ->
Error(SyntaxError(
reason: "Unparseable integer " <> digit_acc,
line: line,
column: option.Some(col),
))
}
}
}

44
src/kielet/tester.gleam Normal file
View file

@ -0,0 +1,44 @@
import gleam/io
import gleam/string
import kielet.{gettext as g_, ngettext as n_}
import kielet/database
import kielet/language
import simplifile
pub fn main() {
let db = database.new()
let lang_code = "fi"
let assert Ok(fi) = simplifile.read_bits("fi.mo")
let assert Ok(lang) = language.load(lang_code, fi)
let db = database.add_language(db, lang)
print(db, "en")
print(db, "fi")
}
fn print(db: database.Database, lang_code: String) {
io.debug(g_(db, "Press to activate", lang_code))
io.debug(string.replace(
n_(
db,
"Press to activate %s button",
"Press to activate %s buttons",
1,
lang_code,
),
"%s",
"1",
))
io.debug(string.replace(
n_(
db,
"Press to activate %s button",
"Press to activate %s buttons",
5,
lang_code,
),
"%s",
"5",
))
}

12
test/kielet_test.gleam Normal file
View file

@ -0,0 +1,12 @@
import gleeunit
import gleeunit/should
pub fn main() {
gleeunit.main()
}
// gleeunit test functions end in `_test`
pub fn hello_world_test() {
1
|> should.equal(1)
}

8
test/mo_test.gleam Normal file
View file

@ -0,0 +1,8 @@
import gleeunit/should
import kielet/mo
import simplifile
pub fn parse_test() {
let assert Ok(data) = simplifile.read_bits("./fi.mo")
should.be_ok(mo.parse(data))
}