Utility: Médiá, formátovanie a pomocníci
Úvod · Index funkcií · Ťahák
Lokalizačné vstavané funkcie a model chýb na tejto stránke sú vydané. Ochrana obsahu, popisky a autorská diagnostika sú draft — špecifikované tak, aby sa okolo nich dal príbeh navrhnúť, ale nepostavené. Každá sekcia nesie vlastnú značku.
22. Skloňovanie podľa počtu a lokalizácia
Rea poskytuje vstavané funkcie pre gramaticky správny text vo všetkých jazykoch. Nahrádzajú akúkoľvek potrebu vlastnej syntaxe podmienok tým, že využívajú pravidlá množného čísla CLDR a štandardné rozhrania internacionalizácie.
Požiadavka: lokál a politiku formátovania dodáva hostiteľ. Kategórie množného čísla a radových čísloviek, zoskupovanie číslic aj štýly dátumu a času sa rozlišujú z CLDR cez
Intlpre lokál dodaný hostiteľom — jadro v sebe nemá zapečenú žiadnu tabuľku pre jednotlivé jazyky. Príbeh sa vykreslí rovnako všade tam, kde hostiteľ deklaruje ten istý lokál.
Skloňovanie pomocou plural()
{plural(n, one="{} coin", other="{} coins")}Kategórie CLDR rozlíšené cez Intl.PluralRules pre lokál hostiteľa, takže v jadre nie je zapečená žiadna tabuľka pre jednotlivé jazyky.
Funkcia plural() mapuje počet na správny gramatický tvar pomocou kategórií množného čísla CLDR. Kategórie sa líšia podľa jazyka — angličtina má 2 (one, other), slovenčina 4 (one, few, many, other), arabčina 6.
{plural(gold, zero="žiadne mince", one="{} minca", other="{} mincí")}Pre 0: „žiadne mince", pre 1: „1 minca", pre 5: „5 mincí". Zástupný znak {} vloží hodnotu počtu.
Slovenčina (4 kategórie):
{plural(count, one="{} pero", few="{} perá", other="{} pier")}Pre 1: „1 pero", pre 3: „3 perá", pre 5: „5 pier".
Šablóna s {} patrí do textu, nie do {set} Volanie napíšte tam, kde chcete jeho text (skrátená tlač):
Máš {plural(story.hrdina.mince, one="{} mincu", other="{} mincí")}.{set} zástupný znak {} neunesie: blok {set} sa končí pri prvej }, na ktorú narazí — a to je práve tá vo vašej šablóne. Ak slovo naozaj potrebujete v premennej, vynechajte zástupný znak a počet si pripojte sami:
{set story.slovo = plural(story.hrdina.mince, one="mincu", few="mince", many="mincí", other="mincí")}
Máš {story.hrdina.mince} {story.slovo}.Kategórie množného čísla CLDR:
| Kategória | Príklad v angličtine | Používajú |
|---|---|---|
zero | 0 položiek | arabčina, lotyština, waleština |
one | 1 položka | väčšina jazykov |
two | 2 položky | arabčina, hebrejčina, slovinčina |
few | 2 – 4 položky | čeština, slovenčina, poľština, ruština |
many | 5 a viac položiek | poľština, ruština, arabčina |
other | predvolená | všetky jazyky (povinná záloha) |
Runtime rozlišuje kategórie cez Intl.PluralRules pre lokál dodaný hostiteľom. Autori uvádzajú len tie kategórie, ktoré ich jazyk vyžaduje — other je povinná záloha a výslovná šablóna zero vždy vyhráva pre počet 0 (ústretovosť voči autorovi, ktorú CLDR pre väčšinu lokálov nemodeluje).
Výber textu pomocou select()
{select(pronoun, he="his", she="her", other="their")}Variácia textu podľa kľúča pre rod, rolu alebo ľubovoľnú inú kategorickú hodnotu.
Funkcia select() mapuje reťazcovú hodnotu na varianty textu. Použite ju pre rod, zámená, roly alebo akúkoľvek variáciu textu podľa kľúča:
{select(pronoun, he="Vytasí svoj meč", she="Vytasí svoj meč", other="Vytasia svoj meč")}other je záloha pre hodnoty, ktorým nič nesedí.
Variácia podľa roly:
{select(story.player.class, warrior="Zaženieš sa čepeľou", mage="Zošleš kúzlo", other="Konáš")}Formátovanie čísel pomocou formatNumber()
{formatNumber(value, "sk", style="currency")}Deleguje na Intl.NumberFormat a pri akejkoľvek chybe sa vráti k jednoduchému reťazcovému tvaru.
Funkcia formatNumber() deleguje na formátovanie čísel podľa lokálu (Intl.NumberFormat). Predvolene používa lokál jadra dodaný hostiteľom; voliteľný druhý pozičný argument ho prebije konkrétnou značkou BCP 47:
Skóre: {formatNumber(story.player.score)}
Lokalizované: {formatNumber(1234567, "sk")}| Parameter | Hodnoty | Predvolené |
|---|---|---|
| (2. pozičný) | Značka lokálu BCP 47 | lokál jadra |
style | decimal, percent, currency | decimal |
currency | Kód ISO 4217 (napr. EUR, USD) | — |
minimumFractionDigits | celé číslo (minimum desatinných miest) | predvolené Intl |
maximumFractionDigits | celé číslo (maximum desatinných miest) | predvolené Intl |
Zoskupovanie (oddeľovače tisícov), počet desatinných miest a symboly sa riadia údajmi CLDR pre daný lokál. Pri akejkoľvek chybe Intl (chybná značka, neplatná kombinácia možností) hodnota padne späť na svoj jednoduchý reťazcový tvar.
Cena: {formatNumber(item.price, style="currency", currency="EUR")}
Šanca: {formatNumber(hit_rate, style="percent")}
Vzdialenosť: {formatNumber(meters, maximumFractionDigits=1)} mFantasy kalendáre pomocou calendar()
{calendar(world.date, month="Frost,Bloom,…")}Mapovanie reálnych zložiek dátumu na vymyslené názvy mesiacov a dní v týždni. Signatúra je ustálená a okolité lokálové vstavané funkcie už fungujú; samotná calendar() sa ešte píše.
Funkcia calendar() mapuje reálne zložky dátumu na vlastné názvy — ideálne na budovanie fantasy sveta:
Mesiac {calendar(context.time.date, month="Mráz,Kvet,Oheň,Dážď,Vietor,Slnko,Búrka,Žatva,Hmla,Tieň,Ľad,Hviezda")}Pre január: „Mráz", pre marec: „Oheň", pre december: „Hviezda".
| Parameter | Popis |
|---|---|
month | Čiarkami oddelený zoznam 12 názvov mesiacov |
weekday | Čiarkami oddelený zoznam 7 názvov dní (pondelok je prvý) |
era | Výraz definujúci výpočet éry |
Deň {calendar(context.time.date, weekday="Mesiacok,Ohnivec,Vodnik,Zemedeň,Vetrovec,Svetlodeň,Temnodeň")},
{calendar(context.time.date, month="Mráz,Kvet,Oheň,Dážď,Vietor,Slnko,Búrka,Žatva,Hmla,Tieň,Ľad,Hviezda")}
{ordinal(context.time.date.day)}Radové číslovky pomocou ordinal()
{ordinal(3)} · {ordinal(n, one="{}.")}Anglické prípony len pre lokály en*; každý iný lokál dostane naformátované číslo, pretože Intl neobsahuje dáta na vypísanie radových čísloviek a vymýšľať prípony by bolo nesprávne.
Skončil si na {ordinal(position)} mieste.Kategória radovej číslovky (one/two/few/other) pochádza z Intl.PluralRules(locale, { type: "ordinal" }) pre lokál dodaný hostiteľom. Bez pomenovaných argumentov pripája ordinal() anglické prípony st, nd, rd a th len pre lokály en*; každý iný lokál dostane číslo naformátované podľa lokálu bez prípony, pretože Intl neobsahuje dáta na vypísanie radových čísloviek a vymýšľať prípony pre jednotlivé jazyky by bolo nesprávne. Autori, ktorí chcú prípony v inom jazyku, odovzdajú šablóny pre jednotlivé kategórie, kde sa {} nahradí naformátovaným číslom:
{ordinal(position, one="{}.", other="{}.")}Takže ordinal(1) je 1st v angličtine a 1 v nemčine; šablónový tvar dá v oboch prípadoch 1..
23. Ochrana obsahu (zámok)
{lock type="soft", key=… begin}Mäkké, tvrdé a podmienené zámky s úplne špecifikovaným modelom AES-GCM a PBKDF2. Nič nie je implementované; najmä tvrdý zámok potrebuje serverovú stranu, ktorá zatiaľ neexistuje.
Príkaz {lock} chráni obsah príbehu a bráni čitateľom v prístupe ku kapitolám, kým nie sú splnené podmienky. Podporuje model postupného sťahovania a speňaženia na platforme.
Mäkký zámok
Obsah je súčasťou balíka, ale skrytý, kým čitateľ nevyrieši hádanku alebo nesplní podmienku. Kľúč sa odvodzuje zo správnej odpovede pomocou PBKDF2 a AES-GCM:
{lock type="soft", key="a1b2c3d4e5f6g7h8i9j0" begin}
Táto kapitola sa odomkne len vtedy, keď čitateľ zadá správnu odpoveď.
{end lock}Viacero platných odpovedí:
{lock type="soft", key=["hash_answer_1", "hash_answer_2"] begin}
Tento obsah odomkne ktorákoľvek z odpovedí.
{end lock}Ako mäkký zámok funguje vnútri:
- Autor pri tvorbe príbehu zadá odpoveď v čistom texte
- Platforma odvodí kľúč AES-256-GCM pomocou PBKDF2 (SHA-256, 100-tisíc iterácií) z odpovede a náhodnej soli
- Zamknutý obsah sa zašifruje odvodeným kľúčom
- Soľ a IV (12 bajtov) sa uložia vedľa šifrovaného textu
- Keď čitateľ odošle odpoveď, platforma kľúč znovu odvodí a pokúsi sa o dešifrovanie
- Vstavaná autentifikačná značka AES-GCM overí, že odpoveď je správna (odolné voči manipulácii)
Tvrdý zámok
Obsah je uložený na serveri a stiahne sa až po tom, čo čitateľ odošle správny kľúč. Bráni to jeho vytiahnutiu z miestneho balíka:
{lock type="hard", key="server_stored_hash" begin}
Táto kapitola sa stiahne až po správnom overení.
{end lock}Tvrdé zámky používajú overenie na strane servera: odpoveď čitateľa sa zahašuje na klientovi a odošle na server, ktorý ju porovná s uloženým odtlačkom a zašifrovaný obsah vráti len pri zhode.
Podmienený zámok
Zamknutie obsahu za podmienky príbehu:
{lock condition="story.player.level >= 10 and has_dragon_scale" begin}
Starodávny text sa odhalí len hodným.
{end lock}Model šifrovania
Všetko šifrovanie obsahu v Rea používa Web Crypto API pre kryptografiu bezpečnú v prehliadači a v súlade so štandardmi:
| Komponent | Algoritmus / štandard |
|---|---|
| Šifrovanie | AES-256-GCM (autentifikované šifrovanie) |
| Odvodenie kľúča | PBKDF2 (SHA-256, 100-tisíc a viac iterácií) |
| IV | 12 náhodných bajtov (na blok, nikdy sa neopakuje) |
| Autentifikačná značka | 128-bitová (súčasť AES-GCM) |
| Výmena kľúčov | X25519 (kooperatívni čitatelia, server – klient) |
| Hašovanie | SHA-256 (kontrolné súčty, overenie odpovede) |
| Podpisovanie | Ed25519 (podpisy balíkov, identita autora) |
Model šifrovania zaisťuje:
- Žiadny čistý text v balíkoch — zamknutý obsah je v súbore
.reastvždy šifrovaný - Dopredné utajenie — každý blok zámku používa jedinečný IV; prelomenie jedného neodhalí ostatné
- Kompatibilita s prehliadačmi — všetky algoritmy fungujú v Chrome, Firefoxe, Safari aj Edge cez
SubtleCrypto - Funkčnosť offline — mäkké zámky sa dešifrujú miestne bez kontaktu so serverom
Kód rozšírenia sa nikdy nešifruje
Ochrana obsahu sa týka len prózy. Zavádzač zašifrované rozšírenie .rext rovno odmietne. Šifrovanie je ochrana obsahu, nie bezpečnostná hranica — sandbox rozšírenie obmedzuje rovnako, či je jeho zdroj zašifrovaný alebo nie — takže jeho zákaz nič obranné nestojí a prináša tri veci: kód sa overí skôr, než sa spustí próza (odomykací kód môže doraziť uprostred príbehu a kód, ktorý sa objaví, až keď je čitateľ zaviazaný, zlyhá v najhoršej chvíli); kód je auditovateľný bez kľúča (reast validate, editor, moderácia platformy); a tretí vkladateľ bez kľúča stále dokáže spustiť logiku príbehu. Celé pravidlo nájdete v Rozšíriteľnosti.
Ak chcete udržať tajomstvo mimo rozšírenia a zároveň ho overovať, nechajte funkciu všeobecnú a v čistom texte a tajomstvo vložte cez {set} do zašifrovanej kapitoly .rea, potom overujte proti tejto premennej namiesto jeho zapečenia:
{comment extensions/gate.rext — čistý text, všeobecný, neobsahuje tajomstvo}
{function unlocked(given, expected) begin}
{return given = expected}
{end function}{comment zašifrovaná kapitola .rea nesie tajomstvo}
{set crypt.passphrase = "moonlit-antler"}Výhrada, povedaná otvorene: zašifrovaná .rea nie je tajomstvom pred odhodlaným čitateľom. Kľúč sa na jeho zariadenie dostane preto, aby sa kapitola vykreslila, takže crypt.passphrase sa dá vytiahnuť. Chráni pred prezradením zápletky, letmým nahliadnutím a prehľadaním archívu — nie pred motivovaným útočníkom. Čokoľvek, čo musí byť skutočne nefalšovateľné (odpoveď v súťaži, platené odomknutie), sa musí overiť na strane servera (pozri Tvrdý zámok), a to je práca platformy, nie jadra.
24. Popisky
{caption "A hand-drawn map"}Popisné titulky pripojené k predchádzajúcemu bloku. Špecifikované a potrebné na splnenie prístupnosti, ale nepostavené — dnes to nesie alternatívny text.
Príkaz {caption} pridáva popisné titulky k predchádzajúcemu obsahu (obrázkom, blokom kódu alebo textovým sekciám):
[!Starodávna mapa < media/map.jpg]
{caption "Ručne kreslená mapa nájdená v čarodejníkovej veži"}
{voice speaker="elena", emotion="sad" begin}
Nikdy som si nemyslela, že to takto skončí.
{end voice}
{caption "Elenine posledné slová"}25. Únikové sekvencie a surový text
Únik pred zvláštnymi znakmi
\{not a command\} · {raw begin} … {end raw}Únik spätnou lomkou a surový blok sú zmrazené — bez nich by príbeh nikdy nemohol citovať vlastnú syntax.
Pomocou \ uniknete pred ľubovoľným znakom so zvláštnym významom:
Cena je \{nie je to príkaz\}.
Použi \_podčiarkovníky\_ bez kurzívy.
Cesta \*nebola\* taká, ako sa zdalo.Surové bloky
Obsah vnútri {raw begin} sa vykreslí tak, ako je, bez akéhokoľvek spracovania:
{raw begin}
Tento {text} sa *nespracúva*.
Neuplatňuje sa tu žiadne _formátovanie_ ani {príkazy}.
{end raw}26. Komentáre
{comment …} · {comment begin} … {end comment}Jedna syntax, jednoriadková aj párová. Blok otvára len presné {comment begin}, takže begin vnútri komentára je len slovo — a práve na to potreboval zrušený tvar {// …} špeciálny režim lexera.
Autorské komentáre (skryté pred čitateľom)
{comment Toto je jednoriadkový komentár}
{comment begin}
Toto je viacriadkový komentár.
Čitatelia ho nikdy neuvidia.
{end comment}Obsah komentára je čistý text až po uzatváraciu zátvorku — bez úvodzoviek. Blok otvára len presné {comment begin}, takže slovo begin vnútri komentára je len slovo: {comment oprav to skôr, než začneme begin} je jednoriadkový komentár.
Viacriadkové komentáre používajú blokovú syntax {comment begin}…{end comment}, v súlade so všetkými ostatnými párovými príkazmi.
Značky TODO
{todo …} · {todo begin} … {end todo}Komentár, ktorý sa sám ohlási: pred čitateľom skrytý ako {comment} a na autorskom kanáli vyvolá style/todo, takže ho reast validate aj editor vypíšu.
{todo Sem napísať bojovú scénu}
{todo begin}
Prepísať záver.
Potom aj stred.
{end todo}TODO je komentár, ktorý sa sám ohlási: pred čitateľom je skrytý presne ako {comment} a na autorskom kanáli vyvolá style/todo, takže ho reast validate aj editor vypíšu. Rovnako ako komentár má čistý textový obsah a blok otvára len {todo begin}.
27. Spracovanie chýb
Dvojkanálový model chýb — tiché náhradné správanie pre čitateľa oproti diagnostickým záznamom pre autora, závažnosti, oblasti kódov, ilustratívna tabuľka a prístup k externým API — má teraz vlastnú stránku: pozri Spracovanie chýb.
28. Príbeh v jednom súbore
{define manifest …} · {define file "assets/x.webp" begin}Všetko, čo drží .reast, vyjadrené v jednom textovom súbore: vlastné metadáta aj vlastné prílohy, adresované tými istými cestami relatívnymi k archívu.
Príbeh prichádza presne v jednom z dvoch tvarov a každý má vlastné pravidlo, kde žijú metadáta a prílohy:
- Archív
.reast.manifest.jsonje povinný a je jediným miestom metadát; prílohy žijú podassets/. Žiadny súbor.reavnútri archívu nesmie deklarovať manifest - Jediný súbor
.reaodovzdaný priamo jadru — napísaný v inom editore, poslaný mailom, uložený v repozitári — smie deklarovať vlastné metadáta a niesť vlastné obrázky, zvuk a fonty priamo v sebe. Všetko, čo drží.reast, sa dá vyjadriť v jednom textovom súbore
{define manifest} — prvý príkaz alebo nič
{define manifest title="…", language="sk"}Číta sa len ako prvý príkaz súboru, takže nástroj zistí jedným riadkom, či .rea nesie metadáta. V .reast je zakázaný, tam je manifest.json jediný.
{define manifest type="story", title="Posledný lampáš", language="sk", genre="mystery",
audience_min=12, audience_max=99, version="1.0.0"}
# Prvá kapitola
Príbeh začína tu.Blok sa číta len ako prvý príkaz súboru. Kdekoľvek inde sa ignoruje — útržok .rea vložený do iného súboru má radšej neniesť metadáta než zlyhať — a autor sa dozvie prečo. Práve toto pravidlo robí ústupok bezpečným: nástroj zistí, či súbor nesie metadáta, tak, že prečíta jeho úvodný príkaz a skončí, takže .rea zostáva triviálne skenovateľný a nikdy nie je o tri obrazovky nižšie druhý manifest, ktorý si s prvým protirečí.
Atribúty sú polia manifest.json sploštené na skaláre. Zoznamy oddelené čiarkou (tags, author, sensors) sa stanú zoznamami; vekové rozpätie sa splošťuje na audience_min / audience_max; pole parts neexistuje, pretože súbor je tá časť. Jediný súbor bez manifestu zostáva platný — nemá názov, čo je presne to, čo má dnes.
{define file} — prílohy priamo v texte
{define file "assets/x.webp" mime="image/webp" begin}Identifikátorom je cesta, takže [!alt < assets/x.webp] funguje v oboch tvaroch bez zmeny a prevod medzi nimi je čisté vloženie a vypísanie.
{define file "assets/cards/card-role-king.webp" mime="image/webp", encoding="base64" begin}
UklGRuYAAABXRUJQVlA4IN...
{end file}Identifikátorom je cesta. {define file} deklaruje cestu relatívnu k archívu, ktorú by súbor mal vnútri .reast, takže každý existujúci odkaz funguje v oboch tvaroch bez zmeny:
[!Kráľ < assets/cards/card-role-king.webp]
{define card king image="assets/cards/card-role-king.webp" begin} … {end card}Žiadna druhá syntax odkazov, žiadna schéma file://, žiadne prepisovanie príbehu, keď sa presúva medzi tvarmi. Prevod .reast do jedného súboru je vloženie každej prílohy; prevod späť je zapísanie každej na jej cestu.
| Atribút | Význam | Predvolené |
|---|---|---|
| (pozičný) | Cesta relatívna k archívu, ktorú by tento súbor zaberal, v úvodzovkách | povinné |
mime | Typ obsahu, aby ho hostiteľ nemusel hádať z prípony | odvodený z prípony |
encoding | base64 pre binárne dáta, text pre všetko, čo má zostať čitateľné a porovnateľné (SVG, JSON, .rext, vložený .rea) | base64 |
- Telo je doslovné, lexované ako
{raw}: nikdy sa neprehľadáva na príkazy, nikdy sa v ňom nenahrádzajú{premenné}, nie je čo escapovať. Base64 nenesie zložené zátvorky, ale vložené SVG či.rextáno - Deklarovaná cesta nič netieni. Dve deklarácie jednej cesty sú dva zdroje pravdy pre jednu prílohu a hlásia sa, namiesto tichého vyriešenia
- Pozícia je voľná, zvyk je na konci. Parser preskočí telo súboru bez interpretácie, takže veľký blok uprostred príbehu nezablokuje streamovanie; dávať ich na koniec je zvyk, nie pravidlo
- Jeden rozpočet pre celý dokument, 50 MB. Neexistuje limit na súbor — vložená príloha môže mať ľubovoľnú veľkosť, kým sa dokument zmestí. Base64 stojí o tretinu viac než bajty, ktoré nesie; dokument nad rozpočet sa odmietne, namiesto toho, aby vyčerpal pamäť telefónu
- Vložené médium je médium. Vypisuje ho výpis médií a počíta sa do odtlačku obsahu, takže offline predsťahovanie ani deduplikácia nikdy nedospejú k záveru, že príbeh s prílohami vnútri žiadne nemá