diff --git a/.gitignore b/.gitignore index aaacfef..40a59d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.beam *.ez /build diff --git a/gleam.toml b/gleam.toml index 4f0a5a5..3b396fb 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "scriptorium" -version = "1.0.2" +version = "2.0.0" target = "javascript" gleam = "~> 1.1" @@ -30,6 +30,7 @@ gleam_javascript = "~> 0.8" ranged_int = "~> 2.0" bigi = "~> 3.0" simplifile = "~> 1.7" +kielet = ">= 2.0.0 and < 3.0.0" [dev-dependencies] gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml index 3a52357..8187d62 100644 --- a/manifest.toml +++ b/manifest.toml @@ -11,8 +11,10 @@ packages = [ { 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 = "jot", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "jot", source = "hex", outer_checksum = "B20A745707EE60B857249D4533656A52964EA024E844005C4AD8135ED432D66C" }, + { name = "kielet", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "nibble"], otp_app = "kielet", source = "hex", outer_checksum = "C6F91EECDC54EAE7C55DE1679C8E7CC4823259782DB13E656E4EC2A2590005B8" }, { name = "lustre", version = "4.1.8", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "55C3D26AEE6BF6B859927616B1F5BC25841926E4EA6A2A72ECC90D6769560F04" }, { name = "lustre_ssg", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "jot", "lustre", "simplifile", "tom"], source = "local", path = "../ssg" }, + { name = "nibble", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nibble", source = "hex", outer_checksum = "67C6BEBC1AB6D771AB893B4A7B3E66C92668C6E7774C335FEFCD545B06435FE5" }, { name = "ranged_int", version = "2.0.0", build_tools = ["gleam"], requirements = ["bigi", "gleam_stdlib"], otp_app = "ranged_int", source = "hex", outer_checksum = "9FCDA804C1884015FC25F3F8BE429FC450D402F861B5C561464479F5B1162A41" }, { name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" }, { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, @@ -24,6 +26,7 @@ bigi = { version = "~> 3.0" } gleam_javascript = { version = "~> 0.8" } gleam_stdlib = { version = "~> 0.36 or ~> 1.0" } gleeunit = { version = "~> 1.0" } +kielet = { version = ">= 2.0.0 and < 3.0.0" } lustre = { version = "~> 4.1" } lustre_ssg = { path = "../ssg" } ranged_int = { version = "~> 2.0" } diff --git a/priv/locale/autogen.pot b/priv/locale/autogen.pot new file mode 100644 index 0000000..23c8455 --- /dev/null +++ b/priv/locale/autogen.pot @@ -0,0 +1,263 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Mikko Ahlroth +# This file is distributed under the same license as the Scriptorium package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Scriptorium 2.0.0\n" +"Report-Msgid-Bugs-To: mikko@ahlroth.fi\n" +"POT-Creation-Date: 2024-05-30 21:47+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/scriptorium/parser.gleam:119 +msgid "Menu parsing failed: {err}" +msgstr "" + +#: src/scriptorium/renderer.gleam:177 +msgid "Archives for {year}" +msgstr "" + +#: src/scriptorium/renderer.gleam:206 +msgid "Archives for {month} {year}" +msgstr "" + +#: src/scriptorium/rendering/views/base.gleam:95 +#: src/scriptorium/rendering/views/single_post.gleam:47 +msgid "Tags" +msgstr "" + +#: src/scriptorium/rendering/views/base.gleam:103 +msgid "Archives" +msgstr "" + +#: src/scriptorium/rendering/views/list_page.gleam:22 +msgid "Pages" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:20 +msgid "Previous" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:38 +msgid "current page" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:64 +msgid "Next" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:70 +msgid "Read more…" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:88 +msgid "{date}, {time}" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:102 +msgid "Posted on {datetime}." +msgstr "" + +#: src/scriptorium/utils/date.gleam:126 +msgid "January" +msgstr "" + +#: src/scriptorium/utils/date.gleam:127 +msgid "February" +msgstr "" + +#: src/scriptorium/utils/date.gleam:128 +msgid "March" +msgstr "" + +#: src/scriptorium/utils/date.gleam:129 +msgid "April" +msgstr "" + +#: src/scriptorium/utils/date.gleam:130 src/scriptorium/utils/date.gleam:148 +msgid "May" +msgstr "" + +#: src/scriptorium/utils/date.gleam:131 +msgid "June" +msgstr "" + +#: src/scriptorium/utils/date.gleam:132 +msgid "July" +msgstr "" + +#: src/scriptorium/utils/date.gleam:133 +msgid "August" +msgstr "" + +#: src/scriptorium/utils/date.gleam:134 +msgid "September" +msgstr "" + +#: src/scriptorium/utils/date.gleam:135 +msgid "October" +msgstr "" + +#: src/scriptorium/utils/date.gleam:136 +msgid "November" +msgstr "" + +#: src/scriptorium/utils/date.gleam:137 +msgid "December" +msgstr "" + +#: src/scriptorium/utils/date.gleam:144 +msgid "Jan" +msgstr "" + +#: src/scriptorium/utils/date.gleam:145 +msgid "Feb" +msgstr "" + +#: src/scriptorium/utils/date.gleam:146 +msgid "Mar" +msgstr "" + +#: src/scriptorium/utils/date.gleam:147 +msgid "Apr" +msgstr "" + +#: src/scriptorium/utils/date.gleam:149 +msgid "Jun" +msgstr "" + +#: src/scriptorium/utils/date.gleam:150 +msgid "Jul" +msgstr "" + +#: src/scriptorium/utils/date.gleam:151 +msgid "Aug" +msgstr "" + +#: src/scriptorium/utils/date.gleam:152 +msgid "Sep" +msgstr "" + +#: src/scriptorium/utils/date.gleam:153 +msgid "Oct" +msgstr "" + +#: src/scriptorium/utils/date.gleam:154 +msgid "Nov" +msgstr "" + +#: src/scriptorium/utils/date.gleam:155 +msgid "Dec" +msgstr "" + +#: src/scriptorium/utils/date.gleam:161 +msgid "{day} {month_short_str} {year}" +msgstr "" + +#: src/scriptorium/utils/time.gleam:44 +msgid "{h24_0}:{min_0}" +msgstr "" + +#: src/scriptorium/utils/time.gleam:77 +msgid "<00> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:78 +msgid "<01> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:79 +msgid "<02> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:80 +msgid "<03> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:81 +msgid "<04> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:82 +msgid "<05> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:83 +msgid "<06> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:84 +msgid "<07> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:85 +msgid "<08> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:86 +msgid "<09> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:87 +msgid "<10> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:88 +msgid "<11> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:89 +msgid "<12> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:90 +msgid "<13> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:91 +msgid "<14> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:92 +msgid "<15> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:93 +msgid "<16> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:94 +msgid "<17> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:95 +msgid "<18> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:96 +msgid "<19> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:97 +msgid "<20> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:98 +msgid "<21> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:99 +msgid "<22> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:100 +msgid "<23> pm" +msgstr "" diff --git a/priv/locale/fi/LC_MESSAGES/messages.mo b/priv/locale/fi/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..012dbcc Binary files /dev/null and b/priv/locale/fi/LC_MESSAGES/messages.mo differ diff --git a/priv/locale/fi/LC_MESSAGES/messages.po b/priv/locale/fi/LC_MESSAGES/messages.po new file mode 100644 index 0000000..5d69d92 --- /dev/null +++ b/priv/locale/fi/LC_MESSAGES/messages.po @@ -0,0 +1,264 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Mikko Ahlroth +# This file is distributed under the same license as the Scriptorium package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Scriptorium 2.0.0\n" +"Report-Msgid-Bugs-To: mikko@ahlroth.fi\n" +"POT-Creation-Date: 2024-05-30 21:47+0300\n" +"PO-Revision-Date: 2024-05-30 21:48+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" +"X-Generator: Poedit 3.4.4\n" + +#: src/scriptorium/parser.gleam:119 +msgid "Menu parsing failed: {err}" +msgstr "Valikon jäsentäminen epäonnistui: {err}" + +#: src/scriptorium/renderer.gleam:177 +msgid "Archives for {year}" +msgstr "Arkistot vuodelle {year}" + +#: src/scriptorium/renderer.gleam:206 +msgid "Archives for {month} {year}" +msgstr "Arkistot {month}lle {year}" + +#: src/scriptorium/rendering/views/base.gleam:95 +#: src/scriptorium/rendering/views/single_post.gleam:47 +msgid "Tags" +msgstr "Tagit" + +#: src/scriptorium/rendering/views/base.gleam:103 +msgid "Archives" +msgstr "Arkistot" + +#: src/scriptorium/rendering/views/list_page.gleam:22 +msgid "Pages" +msgstr "Sivut" + +#: src/scriptorium/rendering/views/nav.gleam:20 +msgid "Previous" +msgstr "Edellinen" + +#: src/scriptorium/rendering/views/nav.gleam:38 +msgid "current page" +msgstr "tämä sivu" + +#: src/scriptorium/rendering/views/nav.gleam:64 +msgid "Next" +msgstr "Seuraava" + +#: src/scriptorium/rendering/views/single_post.gleam:70 +msgid "Read more…" +msgstr "Lue lisää…" + +#: src/scriptorium/rendering/views/single_post.gleam:88 +msgid "{date}, {time}" +msgstr "{date} klo {time}" + +#: src/scriptorium/rendering/views/single_post.gleam:102 +msgid "Posted on {datetime}." +msgstr "Julkaistu {datetime}." + +#: src/scriptorium/utils/date.gleam:126 +msgid "January" +msgstr "tammikuu" + +#: src/scriptorium/utils/date.gleam:127 +msgid "February" +msgstr "helmikuu" + +#: src/scriptorium/utils/date.gleam:128 +msgid "March" +msgstr "maaliskuu" + +#: src/scriptorium/utils/date.gleam:129 +msgid "April" +msgstr "huhtikuu" + +#: src/scriptorium/utils/date.gleam:130 src/scriptorium/utils/date.gleam:148 +msgid "May" +msgstr "toukokuu" + +#: src/scriptorium/utils/date.gleam:131 +msgid "June" +msgstr "kesäkuu" + +#: src/scriptorium/utils/date.gleam:132 +msgid "July" +msgstr "heinäkuu" + +#: src/scriptorium/utils/date.gleam:133 +msgid "August" +msgstr "elokuu" + +#: src/scriptorium/utils/date.gleam:134 +msgid "September" +msgstr "syyskuu" + +#: src/scriptorium/utils/date.gleam:135 +msgid "October" +msgstr "lokakuu" + +#: src/scriptorium/utils/date.gleam:136 +msgid "November" +msgstr "marraskuu" + +#: src/scriptorium/utils/date.gleam:137 +msgid "December" +msgstr "joulukuu" + +#: src/scriptorium/utils/date.gleam:144 +msgid "Jan" +msgstr "tammi" + +#: src/scriptorium/utils/date.gleam:145 +msgid "Feb" +msgstr "helmi" + +#: src/scriptorium/utils/date.gleam:146 +msgid "Mar" +msgstr "maalis" + +#: src/scriptorium/utils/date.gleam:147 +msgid "Apr" +msgstr "huhti" + +#: src/scriptorium/utils/date.gleam:149 +msgid "Jun" +msgstr "touko" + +#: src/scriptorium/utils/date.gleam:150 +msgid "Jul" +msgstr "kesä" + +#: src/scriptorium/utils/date.gleam:151 +msgid "Aug" +msgstr "heinä" + +#: src/scriptorium/utils/date.gleam:152 +msgid "Sep" +msgstr "syys" + +#: src/scriptorium/utils/date.gleam:153 +msgid "Oct" +msgstr "loka" + +#: src/scriptorium/utils/date.gleam:154 +msgid "Nov" +msgstr "marras" + +#: src/scriptorium/utils/date.gleam:155 +msgid "Dec" +msgstr "joulu" + +#: src/scriptorium/utils/date.gleam:161 +msgid "{day} {month_short_str} {year}" +msgstr "{day}.{month_no}.{year}" + +#: src/scriptorium/utils/time.gleam:44 +msgid "{h24_0}:{min_0}" +msgstr "{h24_0}:{min_0}" + +#: src/scriptorium/utils/time.gleam:77 +msgid "<00> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:78 +msgid "<01> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:79 +msgid "<02> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:80 +msgid "<03> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:81 +msgid "<04> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:82 +msgid "<05> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:83 +msgid "<06> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:84 +msgid "<07> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:85 +msgid "<08> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:86 +msgid "<09> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:87 +msgid "<10> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:88 +msgid "<11> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:89 +msgid "<12> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:90 +msgid "<13> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:91 +msgid "<14> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:92 +msgid "<15> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:93 +msgid "<16> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:94 +msgid "<17> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:95 +msgid "<18> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:96 +msgid "<19> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:97 +msgid "<20> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:98 +msgid "<21> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:99 +msgid "<22> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:100 +msgid "<23> pm" +msgstr "" diff --git a/priv/locale/messages.po b/priv/locale/messages.po new file mode 100644 index 0000000..23c8455 --- /dev/null +++ b/priv/locale/messages.po @@ -0,0 +1,263 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Mikko Ahlroth +# This file is distributed under the same license as the Scriptorium package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Scriptorium 2.0.0\n" +"Report-Msgid-Bugs-To: mikko@ahlroth.fi\n" +"POT-Creation-Date: 2024-05-30 21:47+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/scriptorium/parser.gleam:119 +msgid "Menu parsing failed: {err}" +msgstr "" + +#: src/scriptorium/renderer.gleam:177 +msgid "Archives for {year}" +msgstr "" + +#: src/scriptorium/renderer.gleam:206 +msgid "Archives for {month} {year}" +msgstr "" + +#: src/scriptorium/rendering/views/base.gleam:95 +#: src/scriptorium/rendering/views/single_post.gleam:47 +msgid "Tags" +msgstr "" + +#: src/scriptorium/rendering/views/base.gleam:103 +msgid "Archives" +msgstr "" + +#: src/scriptorium/rendering/views/list_page.gleam:22 +msgid "Pages" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:20 +msgid "Previous" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:38 +msgid "current page" +msgstr "" + +#: src/scriptorium/rendering/views/nav.gleam:64 +msgid "Next" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:70 +msgid "Read more…" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:88 +msgid "{date}, {time}" +msgstr "" + +#: src/scriptorium/rendering/views/single_post.gleam:102 +msgid "Posted on {datetime}." +msgstr "" + +#: src/scriptorium/utils/date.gleam:126 +msgid "January" +msgstr "" + +#: src/scriptorium/utils/date.gleam:127 +msgid "February" +msgstr "" + +#: src/scriptorium/utils/date.gleam:128 +msgid "March" +msgstr "" + +#: src/scriptorium/utils/date.gleam:129 +msgid "April" +msgstr "" + +#: src/scriptorium/utils/date.gleam:130 src/scriptorium/utils/date.gleam:148 +msgid "May" +msgstr "" + +#: src/scriptorium/utils/date.gleam:131 +msgid "June" +msgstr "" + +#: src/scriptorium/utils/date.gleam:132 +msgid "July" +msgstr "" + +#: src/scriptorium/utils/date.gleam:133 +msgid "August" +msgstr "" + +#: src/scriptorium/utils/date.gleam:134 +msgid "September" +msgstr "" + +#: src/scriptorium/utils/date.gleam:135 +msgid "October" +msgstr "" + +#: src/scriptorium/utils/date.gleam:136 +msgid "November" +msgstr "" + +#: src/scriptorium/utils/date.gleam:137 +msgid "December" +msgstr "" + +#: src/scriptorium/utils/date.gleam:144 +msgid "Jan" +msgstr "" + +#: src/scriptorium/utils/date.gleam:145 +msgid "Feb" +msgstr "" + +#: src/scriptorium/utils/date.gleam:146 +msgid "Mar" +msgstr "" + +#: src/scriptorium/utils/date.gleam:147 +msgid "Apr" +msgstr "" + +#: src/scriptorium/utils/date.gleam:149 +msgid "Jun" +msgstr "" + +#: src/scriptorium/utils/date.gleam:150 +msgid "Jul" +msgstr "" + +#: src/scriptorium/utils/date.gleam:151 +msgid "Aug" +msgstr "" + +#: src/scriptorium/utils/date.gleam:152 +msgid "Sep" +msgstr "" + +#: src/scriptorium/utils/date.gleam:153 +msgid "Oct" +msgstr "" + +#: src/scriptorium/utils/date.gleam:154 +msgid "Nov" +msgstr "" + +#: src/scriptorium/utils/date.gleam:155 +msgid "Dec" +msgstr "" + +#: src/scriptorium/utils/date.gleam:161 +msgid "{day} {month_short_str} {year}" +msgstr "" + +#: src/scriptorium/utils/time.gleam:44 +msgid "{h24_0}:{min_0}" +msgstr "" + +#: src/scriptorium/utils/time.gleam:77 +msgid "<00> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:78 +msgid "<01> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:79 +msgid "<02> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:80 +msgid "<03> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:81 +msgid "<04> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:82 +msgid "<05> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:83 +msgid "<06> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:84 +msgid "<07> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:85 +msgid "<08> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:86 +msgid "<09> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:87 +msgid "<10> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:88 +msgid "<11> am" +msgstr "" + +#: src/scriptorium/utils/time.gleam:89 +msgid "<12> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:90 +msgid "<13> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:91 +msgid "<14> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:92 +msgid "<15> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:93 +msgid "<16> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:94 +msgid "<17> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:95 +msgid "<18> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:96 +msgid "<19> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:97 +msgid "<20> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:98 +msgid "<21> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:99 +msgid "<22> pm" +msgstr "" + +#: src/scriptorium/utils/time.gleam:100 +msgid "<23> pm" +msgstr "" diff --git a/src/scriptorium/builder.gleam b/src/scriptorium/builder.gleam index e4b80d1..600ff79 100644 --- a/src/scriptorium/builder.gleam +++ b/src/scriptorium/builder.gleam @@ -15,7 +15,7 @@ pub type BuildError { /// Parse the blog's input files. pub fn parse(config: Configuration) { - config.parser() + config.parser(config) |> result.map_error(ParseError) } diff --git a/src/scriptorium/config.gleam b/src/scriptorium/config.gleam index f72b588..ba78054 100644 --- a/src/scriptorium/config.gleam +++ b/src/scriptorium/config.gleam @@ -1,6 +1,7 @@ //// The configuration is the main way to customize Scriptorium's behaviour. import gleam/option +import kielet/context.{type Context} import scriptorium/compiler.{type CompileDatabase, type Compiler} import scriptorium/models/database.{type Database} import scriptorium/paths.{type PathConfiguration} @@ -22,7 +23,7 @@ pub type ParseError { /// The parser pub type Parser = - fn() -> Result(Database, ParseError) + fn(Configuration) -> Result(Database, ParseError) /// Renders the content of the database into HTML. pub type Renderer = @@ -90,17 +91,26 @@ pub type Author { ) } +/// Localization related configuration. +pub type Localization { + Localization( + /// Language code of the blog, meaning the language of the content you are + /// writing. The language code must be according to + /// . + language: String, + /// Context to use for gettext operations for translating built-in strings. + context: Context, + ) +} + pub type Configuration { Configuration( /// Name of the blog. blog_name: String, /// Absolute URL of the blog, this is where you are deploying it. blog_url: String, - /// Language code of the blog, meaning the language of the content you are - /// writing. The language code must be according to - /// . - language: String, author: Author, + l10n: Localization, compiling: Compiling, rendering: Rendering, paths: PathConfiguration, diff --git a/src/scriptorium/defaults.gleam b/src/scriptorium/defaults.gleam index 3ad4060..82db3fb 100644 --- a/src/scriptorium/defaults.gleam +++ b/src/scriptorium/defaults.gleam @@ -1,10 +1,14 @@ //// This module contains default configurations that can be used to create a //// blog quickly. -import gleam/uri import gleam/string +import gleam/uri +import kielet/context.{Context} import scriptorium/compiler -import scriptorium/config.{type Configuration, Compiling, Configuration, Rendering} +import scriptorium/config.{ + type Configuration, Compiling, Configuration, Localization, Rendering, +} +import scriptorium/l10n import scriptorium/parser import scriptorium/paths import scriptorium/renderer @@ -35,7 +39,6 @@ pub fn default_config( author: config.Author, copyright: String, ) -> Configuration { - // Drop any trailing slash from the blog URL, otherwise a `blog_url` like // "https://my.blog.example/" will end up having a root path of "/" instead // of "". @@ -45,10 +48,15 @@ pub fn default_config( } let assert Ok(parsed_url) = uri.parse(blog_url) + let language_db = l10n.load_builtins() + Configuration( blog_name: blog_name, blog_url: blog_url, - language: language, + l10n: Localization( + language: language, + context: Context(language_db, language), + ), author: author, compiling: Compiling( database_compiler: compiler.compile, diff --git a/src/scriptorium/l10n.gleam b/src/scriptorium/l10n.gleam new file mode 100644 index 0000000..1befd2c --- /dev/null +++ b/src/scriptorium/l10n.gleam @@ -0,0 +1,30 @@ +import kielet/database +import kielet/language +import scriptorium/utils/priv +import simplifile + +const locale_dir = "locale" + +const locale_sub_dir = "LC_MESSAGES" + +const locale_filename = "messages.mo" + +/// Load builtin translations and return a translation database containing them. +pub fn load_builtins() -> database.Database { + let db = database.new() + let assert Ok(fi_data) = + simplifile.read_bits( + priv.path() + <> "/" + <> locale_dir + <> "/" + <> "fi" + <> "/" + <> locale_sub_dir + <> "/" + <> locale_filename, + ) + let assert Ok(fi) = language.load("fi", fi_data) + + database.add_language(db, fi) +} diff --git a/src/scriptorium/parser.gleam b/src/scriptorium/parser.gleam index b137fed..4e0d271 100644 --- a/src/scriptorium/parser.gleam +++ b/src/scriptorium/parser.gleam @@ -4,7 +4,8 @@ import gleam/list import gleam/regex import gleam/result import gleam/string -import scriptorium/config.{type ParseError, ParseError} +import kielet.{gettext as g_} +import scriptorium/config.{type Configuration, type ParseError, ParseError} import scriptorium/models/database.{type Database} import scriptorium/parser/common import scriptorium/parser/menu @@ -16,10 +17,10 @@ import simplifile const default_data_path = "./data" /// The default parser. -pub fn default_parse() -> Result(Database, ParseError) { +pub fn default_parse(config: Configuration) -> Result(Database, ParseError) { parse_posts(database.new(), post_path()) |> result.try(parse_pages(_, page_path())) - |> result.try(parse_menu(_, menu_path())) + |> result.try(parse_menu(_, menu_path(), config)) } /// Parse posts from the given path into the database. @@ -97,7 +98,11 @@ pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) { } /// Parse the menu from the given file into the database. -pub fn parse_menu(db: Database, file: String) -> Result(Database, ParseError) { +pub fn parse_menu( + db: Database, + file: String, + config: Configuration, +) -> Result(Database, ParseError) { case simplifile.verify_is_file(file) { Ok(True) -> { use contents <- result.try( @@ -110,7 +115,11 @@ pub fn parse_menu(db: Database, file: String) -> Result(Database, ParseError) { use menu <- result.try( menu.parse(contents) |> result.map_error(fn(err) { - ParseError("Menu parsing failed: " <> string.inspect(err)) + ParseError(string.replace( + g_(config.l10n.context, "Menu parsing failed: {err}"), + "{err}", + string.inspect(err), + )) }), ) diff --git a/src/scriptorium/renderer.gleam b/src/scriptorium/renderer.gleam index 2527d0f..b3ac5ab 100644 --- a/src/scriptorium/renderer.gleam +++ b/src/scriptorium/renderer.gleam @@ -5,6 +5,8 @@ import gleam/dict.{type Dict} import gleam/int import gleam/list import gleam/result +import gleam/string +import kielet.{gettext as g_} import lustre/element.{type Element} import scriptorium/compiler.{ type CompileDatabase, type CompiledPage, type CompiledPost, @@ -171,7 +173,11 @@ fn render_year_pages( config, compiled_posts, views, - "Archives for " <> int.to_string(year), + string.replace( + g_(config.l10n.context, "Archives for {year}"), + "{year}", + int.to_string(year), + ), config.paths.year(year), element.none(), ) @@ -197,10 +203,12 @@ fn render_month_pages( config, compiled_posts, views, - "Archives for " - <> date.month_to_string(month) - <> " " - <> int.to_string(year), + g_(config.l10n.context, "Archives for {month} {year}") + |> string.replace( + "{month}", + date.month_to_string(month, config.l10n.context), + ) + |> string.replace("{year}", int.to_string(year)), config.paths.month(year, month), element.none(), ), diff --git a/src/scriptorium/rendering/views/archives.gleam b/src/scriptorium/rendering/views/archives.gleam index 7cecf64..63ca096 100644 --- a/src/scriptorium/rendering/views/archives.gleam +++ b/src/scriptorium/rendering/views/archives.gleam @@ -48,7 +48,7 @@ pub fn view(db: Database, config: Configuration) { ], [ text( - date.month_to_string(month) + date.month_to_string(month, config.l10n.context) <> " (" <> int.to_string(ordered_tree.length(posts)) <> ")", diff --git a/src/scriptorium/rendering/views/base.gleam b/src/scriptorium/rendering/views/base.gleam index f99c537..1a5fdc4 100644 --- a/src/scriptorium/rendering/views/base.gleam +++ b/src/scriptorium/rendering/views/base.gleam @@ -1,6 +1,7 @@ import gleam/dict import gleam/list import gleam/option +import kielet.{gettext as g_} import lustre/attribute.{attribute, href, id, name, rel, role, type_} import lustre/element.{type Element, text} import lustre/element/html.{ @@ -49,7 +50,7 @@ fn view( prefix -> prefix <> " · " <> config.blog_name } - html([attribute("lang", config.language)], [ + html([attribute("lang", config.l10n.language)], [ head([], [ meta([attribute("charset", "utf-8")]), meta([ @@ -88,13 +89,21 @@ fn view( case dict.size(database.tags(db)) { 0 -> element.none() _ -> - nav([id("tags"), attribute("aria-label", "Tags")], [ - pre_rendered.tags, - ]) + nav( + [ + id("tags"), + attribute("aria-label", g_(config.l10n.context, "Tags")), + ], + [pre_rendered.tags], + ) }, - nav([id("archives"), attribute("aria-label", "Archives")], [ - pre_rendered.archives, - ]), + nav( + [ + id("archives"), + attribute("aria-label", g_(config.l10n.context, "Archives")), + ], + [pre_rendered.archives], + ), ]), footer([], [ p([], [ diff --git a/src/scriptorium/rendering/views/feed.gleam b/src/scriptorium/rendering/views/feed.gleam index 9e87f8b..c91d4a4 100644 --- a/src/scriptorium/rendering/views/feed.gleam +++ b/src/scriptorium/rendering/views/feed.gleam @@ -17,7 +17,7 @@ pub fn generate(_db: Database, config: Configuration) { let now = luxon.utc_now() let #(posts, _) = list.split(posts, config.rendering.posts_in_feed) - feed([attribute("xml:lang", config.language)], [ + feed([attribute("xml:lang", config.l10n.language)], [ title([], config.blog_name), updated([], luxon.to_iso(now)), link([attribute("href", config.blog_url)]), diff --git a/src/scriptorium/rendering/views/list_page.gleam b/src/scriptorium/rendering/views/list_page.gleam index 7a3fa82..4173a20 100644 --- a/src/scriptorium/rendering/views/list_page.gleam +++ b/src/scriptorium/rendering/views/list_page.gleam @@ -1,11 +1,12 @@ import gleam/list +import kielet.{gettext as g_} +import lustre/attribute.{attribute, class} +import lustre/element +import lustre/element/html.{footer, nav} import scriptorium/config.{type Configuration} import scriptorium/models/database.{type Database} import scriptorium/rendering/views.{type ListInfo} import scriptorium/rendering/views/nav -import lustre/attribute.{attribute, class} -import lustre/element -import lustre/element/html.{footer, nav} pub fn generate(db: Database, config: Configuration) { let single_post_renderer = config.rendering.views.single_post_list(db, config) @@ -15,9 +16,13 @@ pub fn generate(db: Database, config: Configuration) { let footer = case info.total_pages > 1 { True -> - nav([class("page-nav"), attribute("aria-label", "Pages")], [ - footer([], [nav.view(info, info.root_path, config)]), - ]) + nav( + [ + class("page-nav"), + attribute("aria-label", g_(config.l10n.context, "Pages")), + ], + [footer([], [nav.view(info, info.root_path, config)])], + ) False -> none } diff --git a/src/scriptorium/rendering/views/nav.gleam b/src/scriptorium/rendering/views/nav.gleam index 460b158..cf4ae89 100644 --- a/src/scriptorium/rendering/views/nav.gleam +++ b/src/scriptorium/rendering/views/nav.gleam @@ -1,10 +1,11 @@ import gleam/int import gleam/list -import scriptorium/config.{type Configuration} -import scriptorium/rendering/views.{type ListInfo} +import kielet.{gettext as g_} import lustre/attribute.{attribute, href} import lustre/element.{text} import lustre/element/html.{a, li, span, ul} +import scriptorium/config.{type Configuration} +import scriptorium/rendering/views.{type ListInfo} pub fn view(list_info: ListInfo, root_path: String, config: Configuration) { let page_range = list.range(1, list_info.total_pages) @@ -16,7 +17,7 @@ pub fn view(list_info: ListInfo, root_path: String, config: Configuration) { nextprev_link( list_info.current_page > 1, list_info.current_page - 1, - "Previous", + g_(config.l10n.context, "Previous"), "«", root_path, config, @@ -30,7 +31,13 @@ pub fn view(list_info: ListInfo, root_path: String, config: Configuration) { let link_attributes = case page == list_info.current_page { True -> [ - attribute("aria-label", int.to_string(page) <> " (current page)"), + attribute( + "aria-label", + int.to_string(page) + <> " (" + <> g_(config.l10n.context, "current page") + <> ")", + ), attribute("aria-disabled", "true"), ] False -> [] @@ -54,7 +61,7 @@ pub fn view(list_info: ListInfo, root_path: String, config: Configuration) { nextprev_link( list_info.current_page < list_info.total_pages, list_info.current_page + 1, - "Next", + g_(config.l10n.context, "Next"), "»", root_path, config, diff --git a/src/scriptorium/rendering/views/single_post.gleam b/src/scriptorium/rendering/views/single_post.gleam index b519ba5..e4badf4 100644 --- a/src/scriptorium/rendering/views/single_post.gleam +++ b/src/scriptorium/rendering/views/single_post.gleam @@ -1,5 +1,8 @@ import gleam/list import gleam/option +import gleam/string +import kielet.{gettext as g_} +import kielet/context.{type Context} import lustre/attribute.{attribute, class, href} import lustre/element.{type Element, text} import lustre/element/html.{ @@ -40,12 +43,8 @@ fn view(post: CompiledPost, is_full: Bool, _db: Database, config: Configuration) article([class("post"), ..language], [ header([], [ wrap_heading(h2([], [text(post.orig.title)]), post_url, is_full), - p([class("post__time")], [ - text("Posted on "), - post_time(post.orig), - text("."), - ]), - nav([attribute("aria-label", "Tags")], [ + p([class("post__time")], post_time(post.orig, config.l10n.context)), + nav([attribute("aria-label", g_(config.l10n.context, "Tags"))], [ ul( [], list.map(post.orig.tags, fn(tag) { @@ -67,7 +66,9 @@ fn view(post: CompiledPost, is_full: Bool, _db: Database, config: Configuration) case is_full, post.content.short { True, _ | False, option.None -> element.none() False, option.Some(_) -> - footer([], [a([href(post_url)], [text("Read more…")])]) + footer([], [ + a([href(post_url)], [text(g_(config.l10n.context, "Read more…"))]), + ]) }, ]) } @@ -79,22 +80,27 @@ fn wrap_heading(heading: Element(Nil), post_url: String, is_full: Bool) { } } -fn post_time(post: Post) { +fn post_time(post: Post, context: Context) { let post_date = post.get_date(post) - let date_str = date.format(post_date) - let time_str = - option.unwrap( - option.map(post.get_time(post), fn(t) { ", " <> time.format(t) }), - "", - ) - - let human_str = date_str <> time_str + let human_str = case post.get_time(post) { + option.Some(t) -> + g_(context, "{date}, {time}") + |> string.replace("{date}", date.format(post_date, context)) + |> string.replace("{time}", time.format(t, context)) + option.None -> date.format(post_date, context) + } let datetime_str = case post.get_luxon(post) { option.Some(lx) -> luxon.to_iso(lx) option.None -> date.format_iso(post_date) } - time([attribute("datetime", datetime_str)], [text(human_str)]) + let datetime_el = + time([attribute("datetime", datetime_str)], [text(human_str)]) + + let format = g_(context, "Posted on {datetime}.") + string.split(format, "{datetime}") + |> list.map(text) + |> list.intersperse(datetime_el) } diff --git a/src/scriptorium/utils/date.gleam b/src/scriptorium/utils/date.gleam index e44ee46..a372535 100644 --- a/src/scriptorium/utils/date.gleam +++ b/src/scriptorium/utils/date.gleam @@ -2,6 +2,8 @@ import gleam/bool import gleam/int import gleam/order.{type Order, Eq} import gleam/string +import kielet.{gettext as g_} +import kielet/context.{type Context} import scriptorium/utils/ints/day.{type Day} pub type Month { @@ -118,31 +120,61 @@ pub fn month_to_int(month: Month) -> Int { } } -/// Convert a month to an English month name string. -pub fn month_to_string(month: Month) -> String { +/// Convert a month to a month name string. +pub fn month_to_string(month: Month, context: Context) -> String { case month { - Jan -> "January" - Feb -> "February" - Mar -> "March" - Apr -> "April" - May -> "May" - Jun -> "June" - Jul -> "July" - Aug -> "August" - Sep -> "September" - Oct -> "October" - Nov -> "November" - Dec -> "December" + Jan -> g_(context, "January") + Feb -> g_(context, "February") + Mar -> g_(context, "March") + Apr -> g_(context, "April") + May -> g_(context, "May") + Jun -> g_(context, "June") + Jul -> g_(context, "July") + Aug -> g_(context, "August") + Sep -> g_(context, "September") + Oct -> g_(context, "October") + Nov -> g_(context, "November") + Dec -> g_(context, "December") } } -/// Format a date in the format "2 Aug 2024". -pub fn format(date: Date) -> String { - int.to_string(day.to_int(date.day)) - <> " " - <> string.slice(month_to_string(date.month), 0, 3) - <> " " - <> int.to_string(date.year) +/// Convert a month to a short month name string. +pub fn month_to_short_string(month: Month, context: Context) -> String { + case month { + Jan -> g_(context, "Jan") + Feb -> g_(context, "Feb") + Mar -> g_(context, "Mar") + Apr -> g_(context, "Apr") + May -> g_(context, "May") + Jun -> g_(context, "Jun") + Jul -> g_(context, "Jul") + Aug -> g_(context, "Aug") + Sep -> g_(context, "Sep") + Oct -> g_(context, "Oct") + Nov -> g_(context, "Nov") + Dec -> g_(context, "Dec") + } +} + +/// Format a date as a string. +pub fn format(date: Date, context: Context) -> String { + g_(context, "{day} {month_short_str} {year}") + |> string.replace("{day}", int.to_string(day.to_int(date.day))) + |> string.replace( + "{day_0}", + string.pad_left(int.to_string(day.to_int(date.day)), 2, "0"), + ) + |> string.replace("{month_str}", month_to_string(date.month, context)) + |> string.replace( + "{month_short_str}", + month_to_short_string(date.month, context), + ) + |> string.replace("{month_no}", int.to_string(month_to_int(date.month))) + |> string.replace( + "{month_no0}", + string.pad_left(int.to_string(month_to_int(date.month)), 2, "0"), + ) + |> string.replace("{year}", int.to_string(date.year)) } /// Format a date in the ISO 8601 format. diff --git a/src/scriptorium/utils/luxon.gleam b/src/scriptorium/utils/luxon.gleam index 42cfcb3..d2504bb 100644 --- a/src/scriptorium/utils/luxon.gleam +++ b/src/scriptorium/utils/luxon.gleam @@ -8,7 +8,7 @@ pub type DateTime /// Get a Luxon DateTime in the given timezone. pub fn date_time_in_zone(date: Date, time: Time, tz: String) { - let datetime_str = date.format_iso(date) <> "T" <> time.format(time) + let datetime_str = date.format_iso(date) <> "T" <> time.format_iso(time) do_date_time_in_zone(datetime_str, tz) } diff --git a/src/scriptorium/utils/time.gleam b/src/scriptorium/utils/time.gleam index bd55771..2adc499 100644 --- a/src/scriptorium/utils/time.gleam +++ b/src/scriptorium/utils/time.gleam @@ -1,6 +1,8 @@ import gleam/int import gleam/order.{type Order, Eq} import gleam/string +import kielet.{gettext as g_} +import kielet/context.{type Context} import scriptorium/utils/ints/hour.{type Hour} import scriptorium/utils/ints/minute.{type Minute} @@ -32,8 +34,28 @@ pub fn parse(str: String) { } } -/// Format a time to an `hh:mm` format string. -pub fn format(time: Time) -> String { +/// Format a time to a string. +pub fn format(time: Time, context: Context) -> String { + let hrs = hour.to_int(time.hours) + let mins = minute.to_int(time.minutes) + + let period = get_period(hrs, context) + + g_(context, "{h24_0}:{min_0}") + |> string.replace("{h24_0}", pad(int.to_string(hrs))) + |> string.replace("{h24}", int.to_string(hrs)) + |> string.replace("{h12_0}", pad(int.to_string(hrs % 12))) + |> string.replace("{h12}", int.to_string(hrs % 12)) + |> string.replace("{min_0}", pad(int.to_string(mins))) + |> string.replace("{min}", int.to_string(mins)) + |> string.replace( + "{period}", + string.slice(period, 5, string.length(period) - 5), + ) +} + +/// Format a time to an `hh:mm` 24 hour string. +pub fn format_iso(time: Time) { pad(int.to_string(hour.to_int(time.hours))) <> ":" <> pad(int.to_string(minute.to_int(time.minutes))) @@ -49,3 +71,32 @@ pub fn nil_time() { fn pad(part: String) { string.pad_left(part, 2, "0") } + +fn get_period(hour: Int, context: Context) { + case hour { + 0 -> g_(context, "<00> am") + 1 -> g_(context, "<01> am") + 2 -> g_(context, "<02> am") + 3 -> g_(context, "<03> am") + 4 -> g_(context, "<04> am") + 5 -> g_(context, "<05> am") + 6 -> g_(context, "<06> am") + 7 -> g_(context, "<07> am") + 8 -> g_(context, "<08> am") + 9 -> g_(context, "<09> am") + 10 -> g_(context, "<10> am") + 11 -> g_(context, "<11> am") + 12 -> g_(context, "<12> pm") + 13 -> g_(context, "<13> pm") + 14 -> g_(context, "<14> pm") + 15 -> g_(context, "<15> pm") + 16 -> g_(context, "<16> pm") + 17 -> g_(context, "<17> pm") + 18 -> g_(context, "<18> pm") + 19 -> g_(context, "<19> pm") + 20 -> g_(context, "<20> pm") + 21 -> g_(context, "<21> pm") + 22 -> g_(context, "<22> pm") + _ -> g_(context, "<23> pm") + } +}