Files
racket-wiki/translate.rkt
T

241 lines
14 KiB
Racket

#lang racket/base
(require db
racket/file
racket/list
racket/string
"private/config.rkt"
"private/database.rkt")
(provide tr
translations-for
current-language
write-language!
translation-page-slug
translation-page-template)
(define english
(hash
'users "Users" 'translations "Translations" 'orphaned-uploads "Orphaned uploads" 'no-orphaned-uploads "No orphaned uploads." 'uploaded-by "uploaded by" 'last-used "Last used" 'never-referenced "Never referenced by a saved page." 'delete-orphaned-upload-confirm "Delete this orphaned upload?" 'recent "Recent" 'recent-changes "Recent changes" 'bookmarks "Bookmarks" 'bookmark "Bookmark" 'bookmarked "Bookmarked" 'bookmark-section "Bookmark section" 'move "Move" 'remove "Remove" 'no-bookmarks "No bookmarks yet." 'no-recent-pages "No recent pages." 'changed-by "changed by" 'todo "Todo" 'todo-list "Todo list" 'graph "Graph" 'wiki-graph "Wiki graph" 'graph-summary "{pages} pages, {links} links" 'context "context" 'context-graph "Context graph" 'context-graph-summary "{pages} related pages, {links} links" 'admin "Admin" 'role-reader "reader" 'role-editor "editor" 'role-admin "admin"
'search-wiki "Search wiki" 'contents "Contents" 'pages "Pages"
'edit "Edit" 'edit-section "Edit section" 'template "Template" 'no-template "No template" 'replace-with-template "Replace the current page content with template {template}?" 'history "History" 'delete "Delete" 'page-not-found "Page not found"
'cancel "Cancel" 'save "Save" 'page-title "Page title" 'tags "Tags (comma separated)"
'version-summary "Version summary (optional)" 'search "Search" 'page-history "Page history"
'back "Back" 'user-administration "User administration" 'username "Username"
'display-name "Display name" 'initial-password "Initial password" 'create-user "Create user"
'sign-in "Sign in" 'sign-out "Sign out" 'password "Password"
'upload-image "Upload image" 'upload-file "Upload file" 'saved "Saved"
'offline "Offline" 'online "Online" 'todo-items "Todo items"
'no-todos "No todo items." 'no-matching-pages "No matching pages."
'this-page-missing "This page does not exist yet." 'no-pages "No pages yet"
'view "View" 'compare-previous "Compare previous" 'new-password "New password"
'created "Created" 'modified "Modified" 'version "Version" 'tags-none "Tags: none"
'you-are-here "You are here"
'setup-title "Set up Racket Wiki" 'setup-description "Configure PostgreSQL, create the first administrator and install the browser libraries. This page does not require JavaScript."
'postgresql "PostgreSQL" 'server "Server" 'port "Port" 'database "Database" 'user "User"
'tls-ssl "TLS/SSL" 'ssl-no "No (typical localhost setup)" 'ssl-optional "Optional" 'ssl-required "Required"
'administrator "Administrator" 'administrator-username "Administrator username" 'administrator-password "Administrator password"
'repeat-password "Repeat password" 'ready "Ready" 'required "Required" 'administrator-account "Administrator account"
'frontend-libraries "Frontend libraries" 'will-download "Will be downloaded" 'complete-setup "Complete setup"
'tags-label "Tags: " 'page-address "Page address" 'page-address-generated "Page address will be generated from the title when you save."
'saving "Saving…" 'created-page "Created page" 'edited-page "Edited page" 'line "line"
'bold "Bold" 'italic "Italic" 'strikethrough "Strikethrough" 'heading "Heading" 'quote "Quote"
'bulleted-list "Bulleted list" 'numbered-list "Numbered list" 'checklist "Checklist" 'code-block "Code block"
'table "Table" 'link "Link" 'horizontal-rule "Horizontal rule" 'undo "Undo" 'redo "Redo"
'preview "Preview" 'side-by-side "Side by side" 'fullscreen "Fullscreen" 'raw-markdown "Raw Markdown" 'automatic-wiki-link "Automatic wiki link"
'language "Language" 'language-en "English" 'language-nl "Dutch"
'no-headings "No headings" 'uploading "Uploading" 'image-upload-complete "Image upload complete" 'upload-complete "Upload complete"
'by "by" 'result "result" 'results "results" 'for "for" 'page-does-not-exist "The page does not exist."))
(define dutch
(hash
'users "Gebruikers" 'translations "Vertalingen" 'orphaned-uploads "Verweeste uploads" 'no-orphaned-uploads "Geen verweeste uploads." 'uploaded-by "geüpload door" 'last-used "Laatst gebruikt" 'never-referenced "Nooit door een opgeslagen pagina gerefereerd." 'delete-orphaned-upload-confirm "Deze verweeste upload verwijderen?" 'recent "Recent" 'recent-changes "Recent gewijzigd" 'bookmarks "Bookmarks" 'bookmark "Bookmark" 'bookmarked "Gebookmarkt" 'bookmark-section "Bookmark-hoofdstuk" 'move "Verplaatsen" 'remove "Verwijderen" 'no-bookmarks "Nog geen bookmarks." 'no-recent-pages "Nog geen recent gewijzigde pagina's." 'changed-by "gewijzigd door" 'todo "Todo" 'todo-list "Todo-lijst" 'graph "Graph" 'wiki-graph "Wiki-graaf" 'graph-summary "{pages} pagina's, {links} links" 'context "context" 'context-graph "Contextgraaf" 'context-graph-summary "{pages} gerelateerde pagina's, {links} links" 'admin "Admin" 'role-reader "lezer" 'role-editor "redacteur" 'role-admin "admin"
'search-wiki "Wiki doorzoeken" 'contents "Inhoud" 'pages "Pagina's"
'edit "Bewerken" 'edit-section "Sectie bewerken" 'template "Sjabloon" 'no-template "Geen sjabloon" 'replace-with-template "De huidige pagina-inhoud vervangen door sjabloon {template}?" 'history "Geschiedenis" 'delete "Verwijderen" 'page-not-found "Pagina niet gevonden"
'cancel "Annuleren" 'save "Opslaan" 'page-title "Paginatitel" 'tags "Tags (komma-gescheiden)"
'version-summary "Versiesamenvatting (optioneel)" 'search "Zoeken" 'page-history "Paginageschiedenis"
'back "Terug" 'user-administration "Gebruikersbeheer" 'username "Gebruikersnaam"
'display-name "Weergavenaam" 'initial-password "Initieel wachtwoord" 'create-user "Gebruiker aanmaken"
'sign-in "Inloggen" 'sign-out "Afmelden" 'password "Wachtwoord"
'upload-image "Afbeelding uploaden" 'upload-file "Bestand uploaden" 'saved "Opgeslagen"
'offline "Offline" 'online "Online" 'todo-items "Todo-items"
'no-todos "Geen todo-items." 'no-matching-pages "Geen overeenkomende pagina's."
'this-page-missing "Deze pagina bestaat nog niet." 'no-pages "Nog geen pagina's"
'view "Bekijken" 'compare-previous "Vergelijk vorige" 'new-password "Nieuw wachtwoord"
'created "Aangemaakt" 'modified "Gewijzigd" 'version "Versie" 'tags-none "Tags: geen"
'you-are-here "U bent hier"
'setup-title "Racket Wiki instellen" 'setup-description "Configureer PostgreSQL, maak de eerste beheerder aan en installeer de browserbibliotheken. Deze pagina heeft geen JavaScript nodig."
'postgresql "PostgreSQL" 'server "Server" 'port "Poort" 'database "Database" 'user "Gebruiker"
'tls-ssl "TLS/SSL" 'ssl-no "Nee (gebruikelijk op localhost)" 'ssl-optional "Optioneel" 'ssl-required "Verplicht"
'administrator "Beheerder" 'administrator-username "Gebruikersnaam beheerder" 'administrator-password "Wachtwoord beheerder"
'repeat-password "Herhaal wachtwoord" 'ready "Gereed" 'required "Verplicht" 'administrator-account "Beheerdersaccount"
'frontend-libraries "Frontendbibliotheken" 'will-download "Worden gedownload" 'complete-setup "Setup voltooien"
'tags-label "Tags: " 'page-address "Pagina-adres" 'page-address-generated "Het pagina-adres wordt bij opslaan uit de titel afgeleid."
'saving "Opslaan…" 'created-page "Pagina aangemaakt" 'edited-page "Pagina gewijzigd" 'line "regel"
'bold "Vet" 'italic "Cursief" 'strikethrough "Doorhalen" 'heading "Kop" 'quote "Citaat"
'bulleted-list "Opsomming" 'numbered-list "Genummerde lijst" 'checklist "Checklist" 'code-block "Codeblok"
'table "Tabel" 'link "Link" 'horizontal-rule "Horizontale lijn" 'undo "Ongedaan maken" 'redo "Opnieuw"
'preview "Voorbeeld" 'side-by-side "Naast elkaar" 'fullscreen "Volledig scherm" 'raw-markdown "Ruwe Markdown" 'automatic-wiki-link "Automatische wikilink"
'language "Taal" 'language-en "Engels" 'language-nl "Nederlands"
'no-headings "Geen koppen" 'uploading "Uploaden" 'image-upload-complete "Afbeelding geüpload" 'upload-complete "Upload voltooid"
'by "door" 'result "resultaat" 'results "resultaten" 'for "voor" 'page-does-not-exist "De pagina bestaat niet."))
(define (base-translations language)
(if (string-ci=? language "nl") dutch english))
(define (current-language config)
(with-handlers ((exn:fail? (λ (_e) (wiki-config-language config))))
(if (file-exists? (language-config-path config))
(call-with-input-file (language-config-path config)
(λ (in)
(define value (read in))
(if (string? value) value (wiki-config-language config))))
(wiki-config-language config))))
(define (write-language! config language)
(make-directory* (wiki-config-data-dir config))
(call-with-output-file (language-config-path config)
(λ (out)
(write language out)
(newline out))
#:exists 'truncate/replace)
(void))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return the slug of the special translations page.
; pre : none; language is accepted for compatibility with older callers.
; post : no state is changed.
; result : The fixed wiki-translations slug.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (translation-page-slug [language #f])
"wiki-translations")
(define (translation-value-needs-quotes? value)
(or (string-contains? value ",")
(string-contains? value "\"")
(not (string=? value (string-trim value)))))
(define (translation-value->text value)
(if (translation-value-needs-quotes? value)
(string-append "\""
(string-replace (string-replace value "\\" "\\\\") "\"" "\\\"")
"\"")
value))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Build the initial contents of the special translations page.
; pre : the built-in translation tables are available.
; post : no state is changed.
; result : Text containing every known key with nl and en values.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (translation-page-template)
(define keys
(sort (remove-duplicates (append (hash-keys english) (hash-keys dutch)))
string<?
#:key symbol->string))
(string-join
(for/list ((key (in-list keys)))
(format "~a = nl:~a, en:~a"
(symbol->string key)
(translation-value->text (hash-ref dutch key (hash-ref english key "")))
(translation-value->text (hash-ref english key ""))))
"\n"))
(define (unquote-translation-value value)
(define text (string-trim value))
(if (and (>= (string-length text) 2)
(char=? (string-ref text 0) #\")
(char=? (string-ref text (sub1 (string-length text))) #\"))
(let ((body (substring text 1 (sub1 (string-length text)))))
(string-replace (string-replace body "\\\"" "\"") "\\\\" "\\"))
text))
(define (split-translation-variants text)
(let loop ((i 0)
(start 0)
(quoted? #f)
(escaped? #f)
(result '()))
(if (>= i (string-length text))
(reverse (cons (substring text start i) result))
(let ((ch (string-ref text i)))
(cond
(escaped?
(loop (add1 i) start quoted? #f result))
((and quoted? (char=? ch #\\))
(loop (add1 i) start quoted? #t result))
((char=? ch #\")
(loop (add1 i) start (not quoted?) #f result))
((and (not quoted?) (char=? ch #\,))
(loop (add1 i) (add1 i) #f #f (cons (substring text start i) result)))
(else
(loop (add1 i) start quoted? #f result)))))))
(define (parse-translation-variants text)
(for/fold ((result (hash)))
((part (in-list (split-translation-variants text))))
(define match (regexp-match #px"^\\s*([A-Za-z][A-Za-z0-9_-]*)\\s*:(.*)$" part))
(if match
(hash-set result
(string-downcase (list-ref match 1))
(unquote-translation-value (list-ref match 2)))
result)))
(define (parse-overrides markdown)
(for/fold ((result (hash)))
((line (in-list (string-split markdown "\n"))))
(define match
(regexp-match #px"^\\s*([A-Za-z0-9._-]+)\\s*=\\s*(.*?)\\s*$" line))
(if match
(let ((variants (parse-translation-variants (list-ref match 2))))
(if (zero? (hash-count variants))
result
(hash-set result (string->symbol (list-ref match 1)) variants)))
result)))
(define (database-overrides config)
(with-handlers ((exn:fail? (λ (_e) (hash))))
(if (not (database-ready? config))
(hash)
(call-with-wiki-database
config
(λ (db)
(define markdown
(query-maybe-value db
"SELECT markdown FROM pages WHERE slug = $1 AND archived = FALSE"
(translation-page-slug)))
(if markdown (parse-overrides markdown) (hash)))))))
(define (translation-override-for-language variants language)
(define language-key (string-downcase language))
(cond
((hash-has-key? variants language-key)
(hash-ref variants language-key))
((hash-has-key? variants "en")
(hash-ref variants "en"))
(else #f)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return all translations active for the configured wiki language.
; pre : config is a wiki-config value.
; post : Translation override page, when available, has only been read.
; result : A hash from translation symbols to strings.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (translations-for config)
(define language (current-language config))
(for/fold ((result (base-translations language)))
(((key variants) (in-hash (database-overrides config))))
(define value (translation-override-for-language variants language))
(if value
(hash-set result key value)
result)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Translate one UI key for the configured wiki language.
; pre : key is a symbol.
; post : Translation data has only been read.
; result : The translated string, or the symbolic key when no translation exists.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (tr config key)
(hash-ref (translations-for config) key (symbol->string key)))