From mboxrd@z Thu Jan 1 00:00:00 1970 From: "pelzflorian (Florian Pelz)" Subject: Re: Website translation Date: Thu, 8 Aug 2019 00:33:10 +0200 Message-ID: <20190807223310.yiwzodu7fwjhvrm6@pelzflorian.localdomain> References: <20190714142625.my2n7ypimctk3vbd@pelzflorian.localdomain> <87ims3o4tm.fsf@gnu.org> <20190715155414.y6lv45rv55ihvzs5@pelzflorian.localdomain> <87muhc8iqi.fsf@gnu.org> <20190718150836.kzf2tmbtng5l42ta@pelzflorian.localdomain> <87h87jffd7.fsf@elephly.net> <20190718202831.4vavgrmogrkpdote@pelzflorian.localdomain> <20190719122952.an6yr6azie4x3xjg@pelzflorian.localdomain> <20190726111155.qospxvrw7rnuwtok@pelzflorian.localdomain> <20190805130827.thp3zzw5ljo6g3h2@pelzflorian.localdomain> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="cyuyic77bivek3sb" Content-Transfer-Encoding: 7bit Return-path: Received: from eggs.gnu.org ([2001:470:142:3::10]:50398) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvUUo-0001NL-Fd for guix-devel@gnu.org; Wed, 07 Aug 2019 18:33:36 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hvUUe-0007Cs-19 for guix-devel@gnu.org; Wed, 07 Aug 2019 18:33:26 -0400 Received: from pelzflorian.de ([5.45.111.108]:42426 helo=mail.pelzflorian.de) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hvUUc-00079N-Q3 for guix-devel@gnu.org; Wed, 07 Aug 2019 18:33:15 -0400 Content-Disposition: inline In-Reply-To: <20190805130827.thp3zzw5ljo6g3h2@pelzflorian.localdomain> List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: "Guix-devel" To: Ricardo Wurmus Cc: guix-devel@gnu.org, sirgazil , matias_jose_seco@autoproduzioni.net --cyuyic77bivek3sb Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Mon, Aug 05, 2019 at 03:08:28PM +0200, pelzflorian (Florian Pelz) wrot= e: > I have implemented a working translation tool. Sexp-xgettext > generates from an SHTML or other Scheme file with marked s-expressions > a POT file which can be translated and turned into an MO file from > which the code generates a localized HTML builder. The advantage is > that existing SHTML will just have to be marked with G_ and no format > string has to be written, although sometimes the SHTML should be > adapted to produce a less complicated message in the POT file. Find > attached an example of a marked Scheme file home.scm generating > guix-website.pot, which after manual translation generates the > attached guix.de_DE.html. >=20 > Marking a string for translation behaves like normal gettext. Marking > a parenthesized expression (i.e. a list or procedure call) extracts > each string from the parenthesized expression. If a symbol, keyword > or other parenthesized expression occurs between the strings, it is > extracted as an XML element. Expressions before or after all strings > are not extracted. If strings from a parenthesized sub-expression > shall be extracted too, the sub-expression must again be marked with > G_ unless it is the only sub-expression or it follows a quote, > unquote, quasiquote or unquote-splicing. The order of XML elements > can be changed in the translation to produce a different ordering > inside a parenthesized expression. If a string shall not be extracted > from a marked expression, it must be wrapped, for example by a call to > the identity procedure. >=20 > But there are still some bugs like missing line numbers and > non-working pgettext and the code is not clear enough yet. I will > send a patch series tomorrow after I fixed these issues (even though > the documentation won=E2=80=99t be near as good as sirgazil=E2=80=99s f= ormat strings). >=20 Find attached patches that add internationalization support, mark the home and about pages for translation and add a sample German translation. Feedback welcome. To use them, generate an MO file and run Haunt by following the instructions in i18n-howto.txt. I have *not* written a Makefile to automate these steps. Sending these patches took longer because new bugs kept appearing. Probably new bugs will show up when marking more files for translation. I will add more markings in the coming days if the patches are OK. I am unsure but I believe the URLs in href links should be marked with G_ as well so translators can change them to the URL of the respective translation of gnu.org, for example. I will make these changes later if you agree. If this internationalization is to be deployed, the NGINX server offering guix.gnu.org would need to redirect according to Accept-Language headers. I do not know if nginx alone can do this properly by now, otherwise there are Lua programs for nginx to handle Accept-Language or a custom Guile webserver could be written. Regards, Florian --cyuyic77bivek3sb Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0001-website-Use-needed-modules-in-posts.patch" >From 979cf0ee1f9276c133626d64b460a52d1702d7c0 Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Thu, 18 Jul 2019 10:22:44 +0200 Subject: [PATCH 1/6] website: Use needed modules in posts. * website/posts/back-from-seagl-2018.sxml: Use needed modules. * website/posts/guix-at-libreplanet-2016.sxml: Use needed modules. --- website/posts/back-from-seagl-2018.sxml | 3 ++- website/posts/guix-at-libreplanet-2016.sxml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/website/posts/back-from-seagl-2018.sxml b/website/posts/back-from-seagl-2018.sxml index c5ad0a9..958369f 100644 --- a/website/posts/back-from-seagl-2018.sxml +++ b/website/posts/back-from-seagl-2018.sxml @@ -1,6 +1,7 @@ (begin (use-modules (apps base templates components) - (srfi srfi-19)) + (apps base utils) + (srfi srfi-19)) `((title . "Back from SeaGL 2018") (author . "Chris Marusich") (date . ,(make-date 0 0 0 0 10 12 2018 -28800)) diff --git a/website/posts/guix-at-libreplanet-2016.sxml b/website/posts/guix-at-libreplanet-2016.sxml index 8581be4..252def3 100644 --- a/website/posts/guix-at-libreplanet-2016.sxml +++ b/website/posts/guix-at-libreplanet-2016.sxml @@ -1,5 +1,6 @@ (begin - (use-modules (srfi srfi-19)) + (use-modules (srfi srfi-19) + (apps base templates components)) `((title . "Guix at LibrePlanet 2016") (author . "David Thompson") (date unquote (make-date 0 0 0 0 15 3 2016 3600)) -- 2.22.0 --cyuyic77bivek3sb Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="0002-website-Add-custom-xgettext-implementation-that-extr.patch" Content-Transfer-Encoding: quoted-printable >From b5b7d9232d5144a4296c3c9c60034628f8146eec Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Wed, 7 Aug 2019 23:48:59 +0200 Subject: [PATCH 2/6] website: Add custom xgettext implementation that ext= racts from nested sexps. * website/scripts/sexp-xgettext.scm: New file for generating a PO file. * website/sexp-xgettext.scm: New file with module for looking up translations. * website/i18n-howto: New file with usage instructions. --- website/i18n-howto.txt | 63 +++ website/scripts/sexp-xgettext.scm | 814 ++++++++++++++++++++++++++++++ website/sexp-xgettext.scm | 454 +++++++++++++++++ 3 files changed, 1331 insertions(+) create mode 100644 website/i18n-howto.txt create mode 100644 website/scripts/sexp-xgettext.scm create mode 100644 website/sexp-xgettext.scm diff --git a/website/i18n-howto.txt b/website/i18n-howto.txt new file mode 100644 index 0000000..66d19d0 --- /dev/null +++ b/website/i18n-howto.txt @@ -0,0 +1,63 @@ +With sexp-xgettext, arbitrary s-expressions can be marked for +translations (not only strings like with normal xgettext). + +S-expressions can be marked with G_ (simple marking for translation), +N_ (=E2=80=9Ccomplex=E2=80=9D marking with different forms depending on = number like +ngettext), C_ (=E2=80=9Ccomplex=E2=80=9D marking distinguished from othe= r markings by +a msgctxt like pgettext) or NC_ (mix of both). + +Marking a string for translation behaves like normal gettext. Marking +a parenthesized expression (i.e. a list or procedure call) extracts +each string from the parenthesized expression. If a symbol, keyword +or other parenthesized expression occurs between the strings, it is +extracted as an XML element. Expressions before or after all strings +are not extracted. If strings from a parenthesized sub-expression +shall be extracted too, the sub-expression must again be marked with +G_ unless it is the only sub-expression or it follows a quote, +unquote, quasiquote or unquote-splicing. The order of XML elements +can be changed in the translation to produce a different ordering +inside a parenthesized expression. If a string shall not be extracted +from a marked expression, it must be wrapped, for example by a call to +the identity procedure. Be careful when marking non-SHTML content +such as procedure calls for translation: Additional strings will be +inserted between non-string elements. + +Known issues: + +* Line numbers are sometimes off. + +* Some less important other TODOs in the comments. + +=3D=3D=3D=3D=3D + +To create a pot file: + +guile scripts/sexp-xgettext.scm -f po/POTFILES -o po/guix-website.pot --= from-code=3DUTF-8 --copyright-holder=3D"Ludovic Court=C3=A8s" --package-n= ame=3D"guix-website" --msgid-bugs-address=3D"ludo@gnu.org" --keyword=3DG_= --keyword=3DN_:1,2 --keyword=3DC_:1c,2 --keyword=3DNC_:1c,2,3 + +To create a po file from a pot file, do the usual: + +cd po +msginit -l de --no-translator + +To merge an existing po file with a new pot file: + +cd po +msgmerge -U de.po guix-website.pot + +To update mo files: + +mkdir -p de/LC_MESSAGES +cd po +msgfmt de.po +cd .. +mv po/messages.mo de/LC_MESSAGES/guix-website.mo + +To test: + +guix environment --ad-hoc haunt +GUILE_LOAD_PATH=3D$(guix build guile-syntax-highlight)/share/guile/site/= 2.2:$GUILE_LOAD_PATH GUIX_WEB_SITE_LOCAL=3Dyes haunt build +GUILE_LOAD_PATH=3D$(guix build guile-syntax-highlight)/share/guile/site/= 2.2:$GUILE_LOAD_PATH haunt serve + +For checking for errors / debugging newly marked files e.g.: + +GUILE_LOAD_PATH=3D.:$(guix build haunt)/share/guile/site/2.2:$GUILE_LOAD= _PATH guile apps/base/templates/about.scm diff --git a/website/scripts/sexp-xgettext.scm b/website/scripts/sexp-xge= ttext.scm new file mode 100644 index 0000000..634c716 --- /dev/null +++ b/website/scripts/sexp-xgettext.scm @@ -0,0 +1,814 @@ +;;; GNU Guix web site +;;; Copyright =C2=A9 2019 Florian Pelz +;;; +;;; This file is part of the GNU Guix web site. +;;; +;;; The GNU Guix web site is free software; you can redistribute it and/= or modify it +;;; under the terms of the GNU Affero General Public License as publishe= d by +;;; the Free Software Foundation; either version 3 of the License, or (a= t +;;; your option) any later version. +;;; +;;; The GNU Guix web site is distributed in the hope that it will be use= ful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU Affero General Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General Public Lic= ense +;;; along with the GNU Guix web site. If not, see . + +(use-modules (ice-9 getopt-long) + (ice-9 match) + (ice-9 peg) + (ice-9 receive) + (ice-9 regex) + (ice-9 textual-ports) + (srfi srfi-1) ;lists + (srfi srfi-9) ;records + (srfi srfi-19) ;date + (srfi srfi-26)) ;cut + +;;; This script imitates xgettext, but combines nested s-expressions +;;; in the input Scheme files to a single msgstr in the PO file. It +;;; works by first reading the keywords specified on the command-line, +;;; then dealing with the remaining options using (ice-9 getopt-long). +;;; Then, it parses each Scheme file in the POTFILES file specified +;;; with --files-from and constructs po entries from it. For parsing, +;;; a PEG is used instead of Scheme=E2=80=99s read, because we can extra= ct +;;; comments with it. The po entries are written to the PO file +;;; specified with the --output option. Scheme code can then use the +;;; (sexp-xgettext) module to deconstruct the msgids looked up in the +;;; PO file via gettext. + +(define-record-type + (make-keyword-spec id sg pl c total xcomment) + keyword-spec? + (id keyword-spec-id) ;identifier + (sg keyword-spec-sg) ;arg with singular + (pl keyword-spec-pl) ;arg with plural + (c keyword-spec-c) ;arg with msgctxt or 'mixed if sg is mixed msgctxt|= singular + (total keyword-spec-total) ;total number of args + (xcomment keyword-spec-xcomment)) + +(define (complex-keyword-spec? keyword-spec) + (match keyword-spec + (($ _ _ #f #f _ #f) #f) + (else #t))) + +(define %keyword-specs + ;; List of valid xgettext keyword options. + ;; Read keywords from command-line options. + (let loop ((opts (cdr (command-line)));command-line options from + ;which to extract --keyword + ;options + (remaining-opts '()) ;unhandled opts + (specs '())) + (define (string->integer str) + (if (string-match "[0-9]+" str) + (string->number str) + (error "Not a decimal integer."))) + (define* (argnums->spec id #:optional (argnums '())) + (let loop ((sg #f) + (pl #f) + (c #f) + (total #f) + (xcomment #f) + (argnums argnums)) + (match argnums + (() (make-keyword-spec id + (if sg sg 1) + pl + c + total + xcomment)) + ((arg . argnums) + (cond + ((string-suffix? "c" arg) + (cond (c (error "c suffix clashes")) + (else + (let* ((number-str (string-drop-right arg 1)) + (number (string->integer number-str))) + (loop sg pl number total xcomment argnums))))) + ((string-suffix? "g" arg) + (cond + (sg (error "Only first argnum can have g suffix.")) + (c (error "g suffix clashes.")) + (else + (let* ((number-str (string-drop-right arg 1)) + (number (string->integer number-str))) + (loop number #f 'mixed total xcomment argnums))))) + ((string-suffix? "t" arg) + (cond (total (error "t suffix clashes")) + (else + (let* ((number-str (string-drop-right arg 1)) + (number (string->integer number-str))) + (loop sg pl c number xcomment argnums))))) + ((string-suffix? "\"" arg) + (cond (xcomment (error "xcomment clashes")) + (else + (let* ((comment (substring arg + 1 + (- (string-length arg) 1)= ))) + (loop sg pl c total comment argnums))))) + (else + (let* ((number (string->integer arg))) + (if sg + (if pl + (error "Too many argnums.") + (loop sg number c total xcomment argnums)) + (loop number #f c total xcomment argnums))))))))) + + (define (string->spec str) ;see `info xgettext` + (match (string-split str #\:) + ((id) (argnums->spec id)) + ((id argnums) + (argnums->spec id (string-split argnums #\,))))) + (match opts + (() (begin + ;; remove recognized --keyword command-line options: + (set-program-arguments (cons (car (command-line)) + (reverse remaining-opts))) + specs)) + ((current-opt . rest) + (cond + ((string=3D? "--" current-opt) specs) + ((string-prefix? "--keyword=3D" current-opt) + (let ((keyword (string-drop current-opt (string-length "--keywo= rd=3D")))) + (loop rest remaining-opts (cons (string->spec keyword) specs)= ))) + ((or (string=3D? "--keyword" current-opt) + (string=3D? "-k" current-opt)) + (let ((next-opt (car rest))) + (loop (cdr rest) + remaining-opts + (cons (string->spec next-opt) specs)))) + (else (loop rest (cons current-opt remaining-opts) specs))))))) + +;;; Other options are not repeated, so we can use getopt-long: + +(define %options ;; Corresponds to what is documented at `info xgettext`= . + (let ((option-spec + `((files (single-char #\f) (value #t)) + (directory (single-char #\D) (value #t)) + (default-domain (single-char #\d) (value #t)) + (output (single-char #\o) (value #t)) + (output-dir (single-char #\p) (value #t)) + (from-code (value #t)) + (join-existing (single-char #\j) (value #f)) + (exclude-file (single-char #\x) (value #t)) + (add-comments (single-char #\c) (value #t)) + + ;; Because getopt-long does not support repeated options, + ;; we took care of --keyword options further up. + ;; (keyword (single-char #\k) (value #t)) + + (flag (value #t)) + (force-po (value #f)) + (indent (single-char #\i) (value #f)) + (no-location (value #f)) + (add-location (single-char #\n) (value #t)) + (width (single-char #\w) (value #t)) + (no-wrap (value #f)) + (sort-output (single-char #\s) (value #f)) + (sort-by-file (single-char #\F) (value #f)) + (omit-header (value #f)) + (copyright-holder (value #t)) + (foreign-user (value #f)) + (package-name (value #t)) + (package-version (value #t)) + (msgid-bugs-address (value #t)) + (msgstr-prefix (single-char #\m) (value #t)) + (msgstr-suffix (single-char #\m) (value #t)) + (help (value #f)) + (pack (value #f))))) + (getopt-long (command-line) option-spec))) + + +(define parse-scheme-file + ;; This procedure parses FILE and returns a parse tree. + (let () + ;;TODO: Optionally ignore case. + (define-peg-pattern NL all "\n") + (define-peg-pattern comment all (and ";" + (* (and peg-any + (not-followed-by NL))) + (and peg-any (followed-by NL)))= ) + (define-peg-pattern empty none (or " " "\t")) + (define-peg-pattern whitespace body (or empty NL)) + (define-peg-pattern quotation body (or "'" "`" "," ",@")) + ;TODO: Allow user to specify + ;other quote reader macros to + ;be ignored and also ignore + ;quote spelled out without + ;reader macro. + (define-peg-pattern open body (and (? quotation) + (or "(" "[" "{"))) + (define-peg-pattern close body (or ")" "]" "}")) + (define-peg-pattern string body (and (followed-by "\"") + (* (or "\\\"" + (and (or NL peg-any) + (not-followed-by "\= "")))) + (and (or NL peg-any) + (followed-by "\"")) + "\"")) + (define-peg-pattern token all (or string + (and + (not-followed-by open) + (not-followed-by close) + (not-followed-by comment) + (* (and peg-any + (not-followed-by open) + (not-followed-by close) + (not-followed-by comment) + (not-followed-by string) + (not-followed-by whitespa= ce))) + (or + (and peg-any (followed-by open)) + (and peg-any (followed-by close)= ) + (and peg-any (followed-by commen= t)) + (and peg-any (followed-by string= )) + (and peg-any (followed-by whites= pace)) + (not-followed-by peg-any))))) + (define-peg-pattern list all (or (and (? quotation) "(" program ")") + (and (? quotation) "[" program "]") + (and (? quotation) "{" program "}")= )) + (define-peg-pattern t-or-s body (or token list)) + (define-peg-pattern program all (* (or whitespace + comment + t-or-s))) + (lambda (file) + (call-with-input-file file + (lambda (port) + ;; It would be nice to match port directly without + ;; converting to a string first, but apparently guile cannot + ;; do that yet. + (let ((string (get-string-all port))) + (peg:tree (match-pattern program string)))))))) + + +(define-record-type + (make-po-entry ecomments ref flags ctxt id idpl) + po-entry? +;;; irrelevant: (tcomments po-entry-tcomments) ;translator-comments + (ecomments po-entry-ecomments) ;extracted-comments + (ref po-entry-ref) ;reference + (flags po-entry-flags) +;;; irrelevant: (prevctxt po-entry-prevctxt) ;previous-ctxt +;;; irrelevant: (prev po-entry-prev) ;previous-translation + (ctxt po-entry-ctxt) ;msgctxt + (id po-entry-id) ;msgid + (idpl po-entry-idpl) ;msgid-plural +;;; irrelevant: (str po-entry-str) ;msgstr string or association list +;;; ;integer to string + ) + +(define (po-equal? po1 po2) + "Returns whether PO1 and PO2 have equal ctxt, id and idpl." + (and (equal? (po-entry-ctxt po1) (po-entry-ctxt po2)) + (equal? (po-entry-id po1) (po-entry-id po2)) + (equal? (po-entry-idpl po1) (po-entry-idpl po2)))) + +(define (combine-duplicate-po-entries list) + "Returns LIST with duplicate po entries replaced by a single PO +entry with both refs." + (let loop ((remaining list)) + (match remaining + (() '()) + ((head . tail) + (receive (before from) + (break (cut po-equal? head <>) tail) + (cond + ((null? from) (cons head (loop tail))) + (else + (loop + (cons + (match head + (($ ecomments1 ref1 flags ctxt id idpl) + (match (car from) + (($ ecomments2 ref2 _ _ _ _) + (let ((ecomments (if (or ecomments1 ecomments2) + (append (or ecomments1 '()) + (or ecomments2 '())) + #f)) + (ref (if (or ref1 ref2) + (string-join + (cons + (or ref1 "") + (cons + (or ref2 "") + '()))) + #f))) + (make-po-entry ecomments ref flags ctxt id idpl))))= )) + (append before (cdr from))))))))))) + +(define (write-po-entry po-entry) + (define (prepare-text text) + "If TEXT is false, returns #f. Otherwise corrects the formatting +of TEXT by escaping backslashes and newlines and enclosing TEXT in +quotes. Note that Scheme=E2=80=99s write is insufficient because it woul= d +escape far more. TODO: Strings should be wrappable to a maximum line +width." + (and text + (string-append "\"" + (with-output-to-string + (lambda () + (call-with-input-string text + (lambda (port) + (let loop ((c (get-char port))) + (unless (eof-object? c) + (case c + ((#\\) (display "\\")) + ((#\newline) (display "\\n")) + (else (write-char c))) + (loop (get-char port)))))))) + "\""))) + (define (write-component c prefix) + (when c + (begin (display prefix) + (display " ") + (display c) + (newline)))) + (match po-entry + (($ ecomments ref flags ctxt id idpl) + (let ((prepared-ctxt (prepare-text ctxt)) + (prepared-id (prepare-text id)) + (prepared-idpl (prepare-text idpl))) + (when ecomments + (for-each + (lambda (line) + (write-component line "#.")) + (reverse ecomments))) + (write-component ref "#:") + (write-component (and flags (string-join flags ", ")) "#,") + (write-component prepared-ctxt "msgctxt") + (write-component prepared-id "msgid") + (write-component prepared-idpl "msgid_plural") + (display "msgstr \"\"") + (newline))))) + +(define %comments-line + (make-parameter #f)) + +(define %ecomments-string + (make-parameter #f)) + +(define (update-ecomments-string! str) + "Sets the value of the parameter object %ecomments-string if str is +an ecomments string. An ecomments string is extracted from a comment +because it starts with TRANSLATORS or a key specified with +--add-comments." ;TODO: Support for other keys is missing. + (cond + ((not str) (%ecomments-string #f)) + ((=3D (1+ (or (%comments-line) -42)) (or (%line-number) 0)) + (let ((m (string-match ";+[ \t]*(.*)" str))) + (when m + (%comments-line (%line-number)) + (%ecomments-string + (if (%ecomments-string) + (cons (match:substring m 1) (%ecomments-string)) + (list (match:substring m 1))))))) + (else + (let ((m (string-match ";+[ \t]*(TRANSLATORS:.*)" str))) + (if m + (begin + (%comments-line (%line-number)) + (%ecomments-string + (if (%ecomments-string) + (cons (match:substring m 1) (%ecomments-string)) + (list (match:substring m 1))))) + (%ecomments-string '#f)))))) + +(define %file-name + (make-parameter #f)) + +(define (update-file-name! name) + "Sets the value of the parameter object %file-name to NAME." + (%file-name name)) + +(define %old-line-number + (make-parameter #f)) + +(define (update-old-line-number! number) + "Sets the value of the parameter object %old-line-number to NUMBER." + (%old-line-number number)) + +(define %line-number + (make-parameter #f)) + +(define (update-line-number! number) + "Sets the value of the parameter object %line-number to NUMBER." + (%line-number number)) + +(define (incr-line-number!) + "Increments the value of the parameter object %line-number by 1." + (%line-number (1+ (%line-number)))) + +(define (incr-line-number-for-each-nl! list) + "Increments %line-number once for each NL recursively in LIST. Does +nothing if LIST is no list but e.g. an empty 'program." + (when (list? list) + (for-each + (lambda (part) + (match part + ('NL (incr-line-number!)) + ((? list?) (incr-line-number-for-each-nl! part)) + (else #f))) + list))) + +(define (current-ref) + "Returns the location field for a PO entry." + (let ((add (option-ref %options 'add-location 'full))) + (cond + ((option-ref %options 'no-location #f) #f) + ((eq? add 'full) + (string-append (%file-name) ":" (number->string (%line-number)))) + ((eq? add 'file) + (%file-name)) + ((eq? add 'never) + #f)))) + +(define (make-simple-po-entry msgid) + (let ((po (make-po-entry + (%ecomments-string) + (current-ref) + #f ;TODO: Use scheme-format for format strings? + #f ;no ctxt + msgid + #f))) + (update-ecomments-string! #f) + po)) + + +(define (matching-keyword id) + "Returns the keyword-spec whose identifier is the same as ID, or #f +if ID is no string or no such keyword-spec exists." + (and (symbol? id) + (let ((found (member (symbol->string id) + %keyword-specs + (lambda (id spec) + (string=3D? id (keyword-spec-id spec)))))) + (and found (car found))))) + +(define (nth-exp program n) + "Returns the Nth 'token or 'list inside the PROGRAM parse tree or #f +if no tokens or lists exist." + (let loop ((i 0) + (rest program)) + (define (on-hit exp) + (if (=3D i n) exp + ;; else: + (loop (1+ i) (cdr rest)))) + (match rest + (() #f) + ((('token . _) . _) (on-hit (car rest))) + ((('list open-paren exp close-paren) . _) (on-hit (car rest))) + ((_ . _) (loop i (cdr rest))) + (else #f)))) + +(define (more-than-one-exp? program) + "Returns true if PROGRAM consiste of more than one expression." + (if (matching-keyword (token->string-symbol-or-keyw (nth-exp program 0= ))) + (nth-exp program 2) ;if there is third element, keyword does not c= ount + (nth-exp program 1))) + +(define (token->string-symbol-or-keyw tok) + "For a parse tree TOK, if it is a 'token parse tree, returns its +value as a string, symbol or #:-keyword, otherwise returns #f." + (match tok + (('token (parts ...) . remaining) + ;; This is a string with line breaks in it. + (with-input-from-string + (string-append + (apply string-append + (map-in-order + (lambda (part) + (match part + (('NL _) + (begin (incr-line-number!) + "\n")) + (else part))) + parts)) + (car remaining)) + (lambda () + (read)))) + (('token exp) + (with-input-from-string exp + (lambda () + (read)))) + (else #f))) + +(define (complex-marked-list->po-entries parse-tree) + "Checks if PARSE-TREE is marked by a keyword. If yes, for a complex +keyword spec, returns a list of po-entries for it. For a simple +keyword spec, returns the argument number of its singular form. +Otherwise returns #f." + (let* ((first (nth-exp parse-tree 0)) + (spec (matching-keyword (token->string-symbol-or-keyw first)))) + (if spec + (if ;if the identifier of a complex keyword occurs first + (complex-keyword-spec? spec) + ;; then make po entries for it + (match spec + (($ id sg pl c total xcomment) + (if (eq? c 'mixed) ; if msgctxt and singular msgid are in on= e string + (let* ((exp (nth-exp parse-tree sg)) + (val (token->string-symbol-or-keyw exp)) + (idx (if (string? val) (string-rindex val #\|)))) + (list + (let ((po (make-po-entry + (%ecomments-string) + (current-ref) + #f ;TODO: Use scheme-format for format str= ings? + (string-take val idx) + (string-drop val (1+ idx)) + #f))) ;plural forms are unsupported here + (update-ecomments-string! #f) + po))) + ;; else construct msgids + (receive (pl-id pl-entries) + (match pl + (#t (construct-msgid-and-po-entries + (nth-exp parse-tree pl))) + (#f (values #f '()))) + (receive (sg-id sg-entries) + (construct-msgid-and-po-entries + (nth-exp parse-tree sg)) + (cons + (let ((po (make-po-entry + (%ecomments-string) + (current-ref) + #f ;TODO: Use scheme-format for format s= trings? + (and c (token->string-symbol-or-keyw + (nth-exp parse-tree c))) + sg-id + pl-id))) + (update-ecomments-string! #f) + po) + (append sg-entries pl-entries))))))) + ;; else if it is a simple keyword, return the argnum: + (keyword-spec-sg spec)) + ;; if no keyword occurs, then false + #f))) + +(define (construct-po-entries parse-tree) + "Converts a PARSE-TREE resulting from a call to parse-scheme-file to +a list of po-entry records. Unlike construct-msgid-and-po-entries, +strings are not collected to a msgid. The list of po-entry records is +the return value." + (let ((entries (complex-marked-list->po-entries parse-tree))) + (cond + ((list? entries) entries) + ((number? entries) ;parse-tree yields a single, simple po entry + (update-old-line-number! (%line-number)) + (receive (id entries) + (construct-msgid-and-po-entries + (nth-exp parse-tree entries)) + (update-line-number! (%old-line-number)) + (let ((po (make-simple-po-entry id))) + (incr-line-number-for-each-nl! parse-tree) + (cons po entries)))) + (else ;search for marked translations in parse-tree + (match parse-tree + (() '()) + (('comment str) (begin + (update-ecomments-string! str) + '())) + (('NL _) (begin (incr-line-number!) '())) + (('token . _) (begin (incr-line-number-for-each-nl! parse-tree) = '())) + (('list open-paren program close-paren) + (construct-po-entries program)) + (('program . components) + (append-map construct-po-entries components)) + ;; Note: PEG compresses empty programs to non-lists: + ('program + '())))))) + +(define* (tag counter prefix #:key (flavor 'start)) + "Formats the number COUNTER as a tag according to FLAVOR, which is +either 'start, 'end or 'empty for a start, end or empty tag, +respectively." + (string-append "<" + (if (eq? flavor 'end) "/" "") + prefix + (number->string counter) + (if (eq? flavor 'empty) "/" "") + ">")) + +(define-record-type + (make-construct-fold-state msgid-string maybe-part counter po-entries) + construct-fold-state? + ;; msgid constructed so far: + (msgid-string construct-fold-state-msgid-string) + ;; only append this if string follows: + (maybe-part construct-fold-state-maybe-part) + ;; counter for next tag: + (counter construct-fold-state-counter) + ;; complete po entries from marked sub-expressions: + (po-entries construct-fold-state-po-entries)) + +(define* (construct-msgid-and-po-entries parse-tree + #:optional + (prefix "")) + "Like construct-po-entries, but with two return values. The first +is an accumulated msgid constructed from all components in PARSE-TREE +for use in make-po-entry. Non-strings are replaced by tags containing +PREFIX. The second return value is a list of po entries for +sub-expressions marked with a complex keyword spec." + (match parse-tree + (() (values "" '())) + ;; Note: PEG compresses empty programs to non-lists: + ('program (values "" '())) + (('comment str) (begin + (update-ecomments-string! str) + (values "" '()))) + (('NL _) (begin (incr-line-number!) + (error "Program consists only of line break." + `(,(%file-name) ,(%line-number))))) + (('token . _) + (let ((maybe-string (token->string-symbol-or-keyw parse-tree))) + (if (string? maybe-string) + (values maybe-string '()) + (error "Single symbol marked for translation." + `(,maybe-string ,(%file-name) ,(%line-number)))))) + (('list open-paren program close-paren) + ;; parse program instead + (construct-msgid-and-po-entries program prefix)) + (('program (? matching-keyword)) + (error "Double-marked for translation." + `(,parse-tree ,(%file-name) ,(%line-number)))) + (('program . components) + ;; Concatenate strings in parse-tree to a new msgid and add an + ;; tag for each list in between. + (match + (fold + (lambda (component prev-state) + (match prev-state + (($ msgid-string maybe-part + counter po-entries) + (match component + (('comment str) (begin (update-ecomments-string! str) + prev-state)) + (('NL _) (begin (incr-line-number!) + prev-state)) + (('token . _) + (let ((maybe-string (token->string-symbol-or-keyw comp= onent))) + (cond + ((string? maybe-string) + ;; if string, append maybe-string to previous msgi= d + (make-construct-fold-state + (string-append msgid-string maybe-part maybe-stri= ng) + "" + counter + po-entries)) + ((and (more-than-one-exp? components) ;not the only= symbol + (or (string-null? msgid-string) ;no string so= far + (string-suffix? ">" msgid-string))) ;tag = before + prev-state) ;then ignore + (else ;append tag representing the token + (make-construct-fold-state + msgid-string + (string-append + maybe-part + (tag counter prefix #:flavor 'empty)) + (1+ counter) + po-entries))))) + (('list open-paren program close-paren) + (let ((first (nth-exp program 0))) + (incr-line-number-for-each-nl! list) + (match (complex-marked-list->po-entries program) + ((? list? result) + (make-construct-fold-state + msgid-string + (string-append + maybe-part + (tag counter prefix #:flavor 'empty)) + (1+ counter) + (append result po-entries))) + (result + (cond + ((number? result) + (receive (id entries) + (construct-msgid-and-po-entries + program + (string-append prefix + (number->string counter) + ".")) + (make-construct-fold-state + (string-append msgid-string + maybe-part + (tag counter prefix + #:flavor 'start) + id + (tag counter prefix + #:flavor 'end)) + "" + (1+ counter) + (append entries po-entries)))) + ((not (more-than-one-exp? components)) + ;; Singletons do not need to be marked. + (receive (id entries) + (construct-msgid-and-po-entries + program + prefix) + (make-construct-fold-state + id + "" + counter + (append entries po-entries)))) + (else ;unmarked list + (if (string-null? msgid-string) + ;; then ignore + prev-state + ;; else: + (make-construct-fold-state + msgid-string + (string-append + maybe-part + (tag counter prefix #:flavor 'empty)) + (1+ counter) + po-entries)))))))))))) + (make-construct-fold-state "" "" 1 '()) + components) + (($ msgid-string maybe-part counter po-ent= ries) + (values msgid-string po-entries)))))) + +(define scheme-file->po-entries + (compose construct-po-entries + parse-scheme-file)) + +(define %files-from-port + (let ((files-from (option-ref %options 'files #f))) + (if files-from + (open-input-file files-from) + (current-input-port)))) + +(define %source-files + (let loop ((line (get-line %files-from-port)) + (source-files '())) + (if (eof-object? line) + (begin + (close-port %files-from-port) + source-files) + ;; else read file names before comment + (let ((before-comment (car (string-split line #\#)))) + (loop (get-line %files-from-port) + (append + (map match:substring (list-matches "[^ \t]+" line)) + source-files)))))) + +(define %output-po-entries + (fold (lambda (scheme-file po-entries) + (begin + (update-file-name! scheme-file) + (update-line-number! 1) + (update-old-line-number! #f) + (%comments-line #f) + (append (scheme-file->po-entries scheme-file) + po-entries))) + '() + %source-files)) + +(define %output-port + (let ((output (option-ref %options 'output #f)) + (domain (option-ref %options 'default-domain #f))) + (cond + (output (open-output-file output)) + (domain (open-output-file (string-append domain ".po"))) + (else (open-output-file "messages.po"))))) + +(with-output-to-port %output-port + (lambda () + (let ((copyright (option-ref %options 'copyright-holder + "THE PACKAGE'S COPYRIGHT HOLDER")) + (package (option-ref %options 'package-name "PACKAGE")) + (version (option-ref %options 'package-version #f)) + (bugs-email (option-ref %options 'msgid-bugs-address ""))) + (display "# SOME DESCRIPTIVE TITLE.\n") + (display (string-append "# Copyright (C) YEAR " copyright "\n")) + (display (string-append "# This file is distributed under the same= \ +license as the " package " package.\n")) + (display "# FIRST AUTHOR , YEAR.\n") + (display "#\n") + (write-po-entry (make-po-entry #f #f '("fuzzy") #f "" #f)) + (display (string-append "\"Project-Id-Version: " + package + (if version + (string-append " " version) + "") + "\\n\"\n")) + (display (string-append "\"Report-Msgid-Bugs-To: " + bugs-email + "\\n\"\n")) + (display (string-append "\"POT-Creation-Date: " + (date->string (current-date) "~1 ~H:~M~z") + "\\n\"\n")) + (display "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n") + (display "\"Last-Translator: FULL NAME \\n\"\n") + (display "\"Language-Team: LANGUAGE \\n\"\n") + (display "\"Language: \\n\"\n") + (display "\"MIME-Version: 1.0\\n\"\n") + (display "\"Content-Type: text/plain; charset=3DUTF-8\\n\"\n") + (display "\"Content-Transfer-Encoding: 8bit\\n\"\n") + (for-each (lambda (po-entry) + (begin + (newline) + (write-po-entry po-entry))) + (combine-duplicate-po-entries %output-po-entries))))) diff --git a/website/sexp-xgettext.scm b/website/sexp-xgettext.scm new file mode 100644 index 0000000..45ee3df --- /dev/null +++ b/website/sexp-xgettext.scm @@ -0,0 +1,454 @@ +;;; GNU Guix web site +;;; Copyright =C2=A9 2019 Florian Pelz +;;; +;;; This file is part of the GNU Guix web site. +;;; +;;; The GNU Guix web site is free software; you can redistribute it and/= or modify it +;;; under the terms of the GNU Affero General Public License as publishe= d by +;;; the Free Software Foundation; either version 3 of the License, or (a= t +;;; your option) any later version. +;;; +;;; The GNU Guix web site is distributed in the hope that it will be use= ful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU Affero General Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General Public Lic= ense +;;; along with the GNU Guix web site. If not, see . + +(define-module (sexp-xgettext) + #:use-module (ice-9 local-eval) + #:use-module (ice-9 match) + #:use-module (ice-9 receive) + #:use-module (ice-9 regex) + #:use-module (srfi srfi-1) ;lists + #:use-module (srfi srfi-9) ;records + #:export (set-complex-keywords! + set-simple-keywords! + sgettext + sngettext + spgettext + snpgettext)) + +(define %complex-keywords + ;; Use set-complex-keywords! to change this to a list of keywords + ;; for sexp-xgettext functions other than sgettext. + (make-parameter '())) + +(define (set-complex-keywords! kw) + (%complex-keywords kw)) + +(define %simple-keywords + ;; Use set-simple-keywords! to change this to a list of keywords + ;; for sgettext. + (make-parameter '())) + +(define (set-simple-keywords! kw) + (%simple-keywords kw)) + +(define (gettext-keyword? id) + (or (member id (%complex-keywords)) + (member id (%simple-keywords)))) + +;;COPIED FROM scripts/sexp-xgettext.scm: +(define* (tag counter prefix #:key (flavor 'start)) + "Formats the number COUNTER as a tag according to FLAVOR, which is +either 'start, 'end or 'empty for a start, end or empty tag, +respectively." + (string-append "<" + (if (eq? flavor 'end) "/" "") + prefix + (number->string counter) + (if (eq? flavor 'empty) "/" "") + ">")) +;;END COPIED FROM scripts/sexp-xgettext.scm + +;;SEMI-COPIED FROM scripts/sexp-xgettext.scm +(define-record-type + (make-construct-fold-state msgid-string maybe-part counter) + construct-fold-state? + ;; msgid constructed so far + (msgid-string construct-fold-state-msgid-string) + ;; only append this if string follows: + (maybe-part construct-fold-state-maybe-part) + ;; counter for next tag: + (counter construct-fold-state-counter)) +;;END SEMI-COPIED FROM scripts/sexp-xgettext.scm + +(define (sexp->msgid exp) + "Return the msgid as constructed by construct-msgid-and-po-entries +in scripts/sexp-xgettext.scm from the expression EXP." + (let loop ((exp exp) + (prefix "")) + (match exp + (() "") + ((or ('quote inner-exp) + ('quasiquote inner-exp) + ('unquote inner-exp) + ('unquote-splicing inner-exp)) + (loop inner-exp prefix)) + ((first-component . components) + (cond + ((gettext-keyword? first-component) + (error "Double-marked for translation." exp)) + (else + (construct-fold-state-msgid-string + (fold + (lambda (component prev-state) + (match prev-state + (($ msgid-string maybe-part counte= r) + (let inner-loop ((exp component)) + (match exp + ((or (? symbol?) (? keyword?)) + (if (string-null? msgid-string) + ;; ignore symbols at the beginning + prev-state + ;; else make a tag for the symbol + (make-construct-fold-state + msgid-string + (string-append maybe-part + (tag counter prefix #:flavor 'e= mpty)) + (1+ counter)))) + ((? string?) + (make-construct-fold-state + (string-append msgid-string maybe-part exp) "" cou= nter)) + ((? list?) + (match exp + (() ;ignore empty list + prev-state) + ((or (singleton) + ('quote singleton) + ('quasiquote singleton) + ('unquote singleton) + ('unquote-splicing singleton)) + (inner-loop singleton)) + ((components ...) + (cond + ((and (not (null? components)) + (member (car components) (%simple-keyword= s))) + ;; if marked for translation, insert inside ta= g + (make-construct-fold-state + (string-append msgid-string + maybe-part + (tag counter prefix #:flavor '= start) + (loop (cadr components) + (string-append + prefix + (number->string counter= ) + ".")) + (tag counter prefix #:flavor '= end)) + "" + (1+ counter))) + ;; else ignore if first + ((string-null? msgid-string) + prev-state) + ;; else make empty tag + (else (make-construct-fold-state + msgid-string + (string-append + maybe-part + (tag counter prefix #:flavor 'empty)) + (1+ counter)))))))))))) + (make-construct-fold-state "" "" 1) + exp))))) + ((? string?) exp) + (else (error "Single symbol marked for translation." exp))))) + +(define-record-type + (make-deconstruct-fold-state tagged maybe-tagged counter) + deconstruct-fold-state? + ;; XML-tagged expressions as an association list name->expression: + (tagged deconstruct-fold-state-tagged) + ;; associate this not-yet-tagged expression with pre if string + ;; follows, with post if not: + (maybe-tagged deconstruct-fold-state-maybe-tagged) + ;; counter for next tag: + (counter deconstruct-fold-state-counter)) + +(define (deconstruct exp msgstr) + "Return an s-expression like EXP, but filled with the content from +MSGSTR." + (define (find-empty-element msgstr name) + "Returns the regex match structure for the empty tag for XML +element of type NAME inside MSGSTR. If the element does not exist or +is more than the empty tag, #f is returned." + (string-match (string-append "<" (regexp-quote name) "/>") msgstr)) + (define (find-element-with-content msgstr name) + "Returns the regex match structure for the non-empty XML element +of type NAME inside MSGSTR. Submatch 1 is its content. If the +element does not exist or is just the empty tag, #f is returned." + (string-match (string-append "<" (regexp-quote name) ">" + "(.*)" + "") + msgstr)) + (define (get-first-element-name prefix msgstr) + "Returns the name of the first XML element in MSGSTR whose name +begins with PREFIX, or #f if there is none." + (let ((m (string-match + (string-append "<(" (regexp-quote prefix) "[^>/.]+)/?>") m= sgstr))) + (and m (match:substring m 1)))) + (define (prefix+counter prefix counter) + "Returns PREFIX with the number COUNTER appended." + (string-append prefix (number->string counter))) + (let loop ((exp exp) + (msgstr msgstr) + (prefix "")) + (define (unwrap-marked-expression exp) + "Returns two values for an expression EXP containing a (possibly +quoted/unquoted) marking for translation with a simple keyword at its +root. The first return value is a list with the inner expression, the +second is a procedure to wrap the processed inner expression in the +same quotes or unquotes again." + (match exp + (('quote inner-exp) + (receive (unwrapped quotation) + (unwrap-marked-expression inner-exp) + (values unwrapped + (lambda (res) + (list 'quote (quotation res)))))) + (('quasiquote inner-exp) + (receive (unwrapped quotation) + (unwrap-marked-expression inner-exp) + (values unwrapped + (lambda (res) + (list 'quasiquote (quotation res)))))) + (('unquote inner-exp) + (receive (unwrapped quotation) + (unwrap-marked-expression inner-exp) + (values unwrapped + (lambda (res) + (list 'unquote (quotation res)))))) + (('unquote-splicing inner-exp) + (receive (unwrapped quotation) + (unwrap-marked-expression inner-exp) + (values unwrapped + (lambda (res) + (list 'unquote-splicing (quotation res)))))) + ((marking . rest) ;list with marking as car + ;; assume arg to translate is first argument to marking: + (values (list-ref rest 0) identity)))) + (define (assemble-parenthesized-expression prefix tagged) + "Returns a parenthesized expression deconstructed from MSGSTR +with the meaning of XML elements taken from the name->expression +association list TAGGED. The special tags [prefix]pre and +[prefix]post are associated with a list of expressions before or after +all others in the parenthesized expression with the prefix, +respectively, in reverse order." + (append ;prepend pre elements to what is in msgstr + (reverse (or (assoc-ref tagged (string-append prefix "pre")) '())= ) + (let assemble ((rest msgstr)) + (let ((name (get-first-element-name prefix rest))) + (cond + ((and name (find-empty-element rest name)) =3D> + ;; first XML element in rest is empty element + (lambda (m) + (cons* + (match:prefix m) ;prepend string before name + (assoc-ref tagged name) ;and expression for name + (assemble (match:suffix m))))) + ((and name (find-element-with-content rest name)) =3D> + ;; first XML element in rest has content + (lambda (m) + (receive (unwrapped quotation) + (unwrap-marked-expression (assoc-ref tagged name)) + (cons* + (match:prefix m) ;prepend string before name + ;; and the deconstructed element with the content as m= sgstr: + (quotation + (loop + unwrapped + (match:substring m 1) + (string-append name "."))) + (assemble (match:suffix m)))))) + (else + ;; there is no first element + (cons + rest ;return remaining string + (reverse ;and post expressions + (or (assoc-ref tagged (string-append prefix "post")) '())= )))))))) + (match exp + (() '()) + (('quote singleton) + (cons 'quote (list (loop singleton msgstr prefix)))) + (('quasiquote singleton) + (cons 'quasiquote (list (loop singleton msgstr prefix)))) + (('unquote singleton) + (cons 'unquote (list (loop singleton msgstr prefix)))) + (('unquote-splicing singleton) + (cons 'unquote-splicing (list (loop singleton msgstr prefix)))) + ((singleton) + (list (loop singleton msgstr prefix))) + ((first-component . components) + (cond + ((gettext-keyword? first-component) + ;; another marking for translation + ;; -> should be an error anyway; just retain exp + exp) + (else + ;; This handles a single level of a parenthesized expression. + ;; assemble-parenthesized-expression will call loop to + ;; recurse to deeper levels. + (let ((tagged-state + (fold + (lambda (component prev-state) + (match prev-state + (($ tagged maybe-tagged co= unter) + (let inner-loop ((exp component) ;sexp to handle + (quoting identity)) ;for wrapping= state + (define (tagged-with-maybes) + "Returns the value of tagged after adding +all maybe-tagged expressions. This should be used as the base value +for tagged when a string or marked expression is seen." + (match counter + (#f + (alist-cons (string-append prefix "pre") + maybe-tagged + tagged)) + ((? number?) + (let accumulate ((prev-counter counter) + (maybes (reverse maybe-tag= ged))) + (match maybes + (() tagged) + ((head . tail) + (alist-cons + (prefix+counter prefix prev-counter) + head + (accumulate (1+ prev-counter) tail)))= ))))) + (define (add-maybe exp) + "Returns a deconstruct-fold-state with EXP +added to maybe-tagged. This should be used for expressions that are +neither strings nor marked for translation with a simple keyword." + (make-deconstruct-fold-state + tagged + (cons (quoting exp) maybe-tagged) + counter)) + (define (counter-with-maybes) + "Returns the old counter value incremented +by one for each expression in maybe-tagged. This should be used +together with tagged-with-maybes." + (match counter + ((? number?) + (+ counter (length maybe-tagged))) + (#f + 1))) + (define (add-tagged exp) + "Returns a deconstruct-fold-state with an +added association in tagged from the current counter to EXP. If +MAYBE-TAGGED is not empty, associations for its expressions are added +to pre or their respective counter. This should be used for +expressions marked for translation with a simple keyword." + (let ((c (counter-with-maybes))) + (make-deconstruct-fold-state + (alist-cons + (prefix+counter prefix c) + (quoting exp) + (tagged-with-maybes)) + '() + (1+ c)))) + (match exp + (('quote inner-exp) + (inner-loop inner-exp + (lambda (res) + (list 'quote res)))) + (('quasiquote inner-exp) + (inner-loop inner-exp + (lambda (res) + (list 'quasiquote res)))) + (('unquote inner-exp) + (inner-loop inner-exp + (lambda (res) + (list 'unquote res)))) + (('unquote-splicing inner-exp) + (inner-loop inner-exp + (lambda (res) + (list 'unquote-splicing res)))) + (((? gettext-keyword?) . rest) + (add-tagged exp)) + ((or (? symbol?) (? keyword?) (? list?)) + (add-maybe exp)) + ((? string?) + ;; elements in maybe-tagged appear between st= rings + (let ((c (counter-with-maybes))) + (make-deconstruct-fold-state + (tagged-with-maybes) + '() + c)))))))) + (make-deconstruct-fold-state '() '() #f) + exp))) + (match tagged-state + (($ tagged maybe-tagged counter) + (assemble-parenthesized-expression + prefix + (match maybe-tagged + (() tagged) + (else ;associate maybe-tagged with pre or post + (alist-cons + (cond ;if there already is a pre, use post + ((assoc-ref tagged (string-append prefix "pre")) + (string-append prefix "post")) + (else (string-append prefix "pre"))) + maybe-tagged + tagged)))))))))) + ((? string?) msgstr) + (else (error "Single symbol marked for translation." exp))))) + +(define (sgettext x) + "After choosing an identifier for marking s-expressions for +translation, make it usable by defining a macro with it calling +sgettext. If for example the chosen identifier is G_, +use (define-syntax G_ sgettext)." + (syntax-case x () + ((_ exp) + (let* ((msgstr (sexp->msgid (syntax->datum #'exp))) + (new-exp (deconstruct (syntax->datum #'exp) + (gettext msgstr)))) + (datum->syntax #'exp new-exp))))) + +(define (sngettext x) + "After choosing an identifier for behavior similar to ngettext:1,2, +make it usable like (define-syntax N_ sngettext)." + (syntax-case x () + ((_ msgid1 msgid2 n) + (let* ((msgstr1 (sexp->msgid (syntax->datum #'msgid1))) + (msgstr2 (sexp->msgid (syntax->datum #'msgid2))) + (applicable (if (=3D #'n 1) #'msgid1 #'msgid2)) + (new-exp (deconstruct (syntax->datum applicable) + (ngettext msgstr1 msgstr2 #'n)))) + (datum->syntax #'msgid1 new-exp))))) + +;; gettext=E2=80=99s share/gettext/gettext.h tells us we can prepend a m= sgctxt +;; and #\eot before a msgid in a gettext call. + +(define (spgettext x) + "After choosing an identifier for behavior similar to pgettext:1c,2, +make it usable like (define-syntax C_ spgettext)." + (syntax-case x () + ((_ msgctxt exp) + (let* ((gettext-context-glue #\eot) ;as defined in gettext.h + (lookup (string-append (syntax->datum #'msgctxt) + (string gettext-context-glue) + (sexp->msgid (syntax->datum #'exp)))) + (msgstr (car (reverse (string-split (gettext lookup) + gettext-context-glue)))) + (new-exp (deconstruct (syntax->datum #'exp) + msgstr))) + (datum->syntax #'exp new-exp))))) + +(define (snpgettext x) + "After choosing an identifier for behavior similar to npgettext:1c,2,3= , +make it usable like (define-syntax NC_ snpgettext)." + (syntax-case x () + ((_ msgctxt msgid1 msgid2 n) + (let* ((gettext-context-glue #\eot) ;as defined in gettext.h + (lookup1 (string-append (syntax->datum #'msgctxt) + (string gettext-context-glue) + (sexp->msgid (syntax->datum #'msgid1= )))) + ;; gettext.h implementation shows: msgctxt is only part of m= sgid1. + (lookup2 (sexp->msgid (syntax->datum #'msgid2))) + (msgstr (car (reverse + (string-split (gettext (ngettext lookup1 looku= p2 #'n)) + gettext-context-glue)))) + (applicable (if (=3D #'n 1) #'msgid1 #'msgid2)) + (new-exp (deconstruct (syntax->datum applicable) + msgstr))) + (datum->syntax #'msgid1 new-exp))))) --=20 2.22.0 --cyuyic77bivek3sb Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: attachment; filename="0003-website-Use-custom-xgettext-implementation.patch" Content-Transfer-Encoding: quoted-printable >From b59785ef4e51156b69c0c1a8e6e0724dcadddb44 Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Wed, 7 Aug 2019 23:52:31 +0200 Subject: [PATCH 3/6] website: Use custom xgettext implementation. * website/po/POTFILES: New file; list apps/base/templates files here. * website/po/LINGUAS: New file. List en_US lingua. * website/apps/i18n.scm: New file. Add utility functions. * website/haunt.scm: Load linguas and call each builder with each. --- website/apps/i18n.scm | 105 ++++++++++++++++++++++++++++++++++++++++++ website/haunt.scm | 24 +++++++--- website/po/LINGUAS | 1 + website/po/POTFILES | 13 ++++++ 4 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 website/apps/i18n.scm create mode 100644 website/po/LINGUAS create mode 100644 website/po/POTFILES diff --git a/website/apps/i18n.scm b/website/apps/i18n.scm new file mode 100644 index 0000000..fbbbd9f --- /dev/null +++ b/website/apps/i18n.scm @@ -0,0 +1,105 @@ +;;; GNU Guix web site +;;; Copyright =A9 2019 Florian Pelz +;;; +;;; This file is part of the GNU Guix web site. +;;; +;;; The GNU Guix web site is free software; you can redistribute it and/= or modify it +;;; under the terms of the GNU Affero General Public License as publishe= d by +;;; the Free Software Foundation; either version 3 of the License, or (a= t +;;; your option) any later version. +;;; +;;; The GNU Guix web site is distributed in the hope that it will be use= ful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU Affero General Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General Public Lic= ense +;;; along with the GNU Guix web site. If not, see . + +(define-module (apps i18n) + #:use-module (haunt page) + #:use-module (haunt utils) + #:use-module (ice-9 match) + #:use-module (sexp-xgettext) + #:use-module (srfi srfi-1) + #:export (G_ + N_ + C_ + NC_ + %current-lingua + builder->localized-builder + builders->localized-builders)) + +(define %gettext-domain + "guix-website") + +(bindtextdomain %gettext-domain (getcwd)) +(bind-textdomain-codeset %gettext-domain "UTF-8") +(textdomain %gettext-domain) + +(define-syntax G_ + sgettext) + +(set-simple-keywords! '(G_)) + +(define-syntax N_ ;like ngettext + sngettext) + +(define-syntax C_ ;like pgettext + spgettext) + +(define-syntax NC_ ;like npgettext + snpgettext) + +(set-complex-keywords! '(N_ C_ NC_)) + +(define + (@@ (haunt page) )) + +(define %current-lingua + (make-parameter "en_US")) + +(define (first-value arg) + "For some reason the builder returned by static-directory returns +multiple values. This procedure is used to retain only the first +return value. TODO: This should not be necessary." + arg) + +(define (builder->localized-builder builder lingua) + "Returns a Haunt builder procedure generated from an existing +BUILDER with translations for LINGUA coming from sexp-xgettext." + (compose + (lambda (pages) + (map + (lambda (page) + (match page + (($ file-name contents writer) + (if (string-suffix? ".html" file-name) + (let* ((base (string-drop-right + file-name + (string-length ".html"))) + (new-name (string-append base + "." + lingua + ".html"))) + (make-page new-name contents writer)) + page)) + (else page))) + pages)) + (lambda (site posts) + (begin + (setlocale LC_ALL (string-append lingua ".utf8")) + (parameterize ((%current-lingua lingua)) + (first-value (builder site posts))))))) + +(define (builders->localized-builders builders linguas) + "Returns a list of new Haunt builder procedures generated from +BUILDERS and localized via sexp-xgettext for each of the LINGUAS." + (flatten + (map-in-order + (lambda (builder) + (map-in-order + (lambda (lingua) + (builder->localized-builder builder lingua)) + linguas)) + builders))) diff --git a/website/haunt.scm b/website/haunt.scm index d29c0d4..eb0eafe 100644 --- a/website/haunt.scm +++ b/website/haunt.scm @@ -5,13 +5,23 @@ (use-modules ((apps base builder) #:prefix base:) ((apps blog builder) #:prefix blog:) ((apps download builder) #:prefix download:) + (apps i18n) ((apps packages builder) #:prefix packages:) (haunt asset) (haunt builder assets) (haunt reader) (haunt reader commonmark) - (haunt site)) + (haunt site) + (ice-9 rdelim) + (srfi srfi-1)) =20 +(define linguas + (with-input-from-file "po/LINGUAS" + (lambda _ + (let loop ((line (read-line))) + (if (eof-object? line) + '() + (cons line (loop (read-line)))))))) =20 (site #:title "GNU=A0Guix" #:domain (if (getenv "GUIX_WEB_SITE_INFO") @@ -19,8 +29,10 @@ "https://gnu.org/software/guix") #:build-directory "/tmp/gnu.org/software/guix" #:readers (list sxml-reader html-reader commonmark-reader) - #:builders (list base:builder - blog:builder - download:builder - packages:builder - (static-directory "static"))) + #:builders (builders->localized-builders + (list base:builder + blog:builder + download:builder + packages:builder + (static-directory "static")) + linguas)) diff --git a/website/po/LINGUAS b/website/po/LINGUAS new file mode 100644 index 0000000..7741b83 --- /dev/null +++ b/website/po/LINGUAS @@ -0,0 +1 @@ +en_US diff --git a/website/po/POTFILES b/website/po/POTFILES new file mode 100644 index 0000000..4013d52 --- /dev/null +++ b/website/po/POTFILES @@ -0,0 +1,13 @@ +apps/base/templates/about.scm +apps/base/templates/components.scm +apps/base/templates/contact.scm +apps/base/templates/contribute.scm +apps/base/templates/donate.scm +apps/base/templates/graphics.scm +apps/base/templates/help.scm +apps/base/templates/home.scm +apps/base/templates/irc.scm +apps/base/templates/menu.scm +apps/base/templates/screenshot.scm +apps/base/templates/security.scm +apps/base/templates/theme.scm --=20 2.22.0 --cyuyic77bivek3sb Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="0004-website-Mark-some-files-in-apps-base-for-translation.patch" Content-Transfer-Encoding: quoted-printable >From 43fc90e205054333a751b5fb1a73c29c922a367c Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Thu, 8 Aug 2019 00:01:50 +0200 Subject: [PATCH 4/6] website: Mark some files in apps/base for translatio= n. * website/apps/base/templates/about.scm (home-t): Mark for translation. * website/apps/base/templates/components.scm (home-t): Mark for translati= on. * website/apps/base/templates/contact.scm (home-t): Mark for translation. * website/apps/base/templates/home.scm (home-t): Mark for translation. * website/apps/base/templates/theme.scm (home-t): Mark for translation. --- website/apps/base/templates/about.scm | 159 +++++++------- website/apps/base/templates/components.scm | 54 ++--- website/apps/base/templates/contact.scm | 20 +- website/apps/base/templates/home.scm | 239 +++++++++++---------- website/apps/base/templates/theme.scm | 46 ++-- 5 files changed, 275 insertions(+), 243 deletions(-) diff --git a/website/apps/base/templates/about.scm b/website/apps/base/te= mplates/about.scm index a654e2f..ecdc81b 100644 --- a/website/apps/base/templates/about.scm +++ b/website/apps/base/templates/about.scm @@ -6,102 +6,111 @@ #:use-module (apps base templates theme) #:use-module (apps base types) #:use-module (apps base utils) + #:use-module (apps i18n) #:export (about-t)) =20 =20 (define (about-t) "Return the About page in SHTML." (theme - #:title '("About") + #:title (G_ '("About")) #:description - "Guix is an advanced distribution of the GNU operating system. - Guix is technology that respects the freedom of computer users. - You are free to run the system for any purpose, study how it - works, improve it, and share it with the whole world." + (G_ "Guix is an advanced distribution of the GNU operating system. + Guix is technology that respects the freedom of computer users. + You are free to run the system for any purpose, study how it + works, improve it, and share it with the whole world.") #:keywords - (list "GNU" "Linux" "Unix" "Free software" "Libre software" - "Operating system" "GNU Hurd" "GNU Guix package manager") - #:active-menu-item "About" + (string-split ;TRANSLATORS: |-separated list of webpage keywords + (G_ "GNU|Linux|Unix|Free software|Libre software|Operating \ +system|GNU Hurd|GNU Guix package manager") #\|) + #:active-menu-item (C_ "Website menu" "About") #:css (list (guix-url "static/base/css/page.css")) - #:crumbs (list (crumb "About" "./")) + #:crumbs (list (crumb (C_ "Website menu" "About") "./")) #:content `(main (section (@ (class "page centered-block limit-width")) - (h2 "About the Project") + ,(G_ `(h2 "About the Project")) =20 - (p - "The " (em "GNU Guix") " package and system manager is a " - (a (@ (href ,(gnu-url "philosophy/free-sw.html"))) - "free software") - " project developed by volunteers around the world under the - umbrella of the " (a (@ (href ,(gnu-url))) "GNU Project") ". ") + ,(G_ + `(p + "The " ,(G_ `(em "GNU Guix")) " package and system manager is = a " + ,(G_ `(a (@ (href ,(gnu-url "philosophy/free-sw.html"))) + "free software")) + " project developed by volunteers around the world under the + umbrella of the " + ,(G_ `(a (@ (href ,(gnu-url))) "GNU Project")) ". ")) =20 - (p - "Guix System is an advanced distribution of the " - (a (@ (href ,(gnu-url))) "GNU operating system") - ". It uses the " - (a (@ (href ,(gnu-url "software/linux-libre"))) "Linux-libre") - " kernel, and support for " - (a (@ (href ,(gnu-url "software/hurd"))) "the Hurd") - " is being worked on. As a GNU distribution, it is committed - to respecting and enhancing " - (a (@ (href ,(gnu-url "philosophy/free-sw.html"))) - "the freedom of its users") - ". As such, it adheres to the " - (a (@ (href ,(gnu-url "distros/free-system-distribution-guideline= s.html"))) - "GNU Free System Distribution Guidelines") ".") + ,(G_ + `(p + "Guix System is an advanced distribution of the " + ,(G_ `(a (@ (href ,(gnu-url))) "GNU operating system")) + ". It uses the " + ,(G_ `(a (@ (href ,(gnu-url "software/linux-libre"))) "Linux-l= ibre")) + " kernel, and support for " + ,(G_ `(a (@ (href ,(gnu-url "software/hurd"))) "the Hurd")) + " is being worked on. As a GNU distribution, it is committed + to respecting and enhancing " + ,(G_ `(a (@ (href ,(gnu-url "philosophy/free-sw.html"))) + "the freedom of its users")) + ". As such, it adheres to the " + ,(G_ `(a (@ (href ,(gnu-url "distros/free-system-distribution-= guidelines.html"))) + "GNU Free System Distribution Guidelines")) ".")) =20 - (p - "GNU Guix provides " - (a (@ (href ,(manual-url "Features.html"))) - "state-of-the-art package management features") - " such as transactional upgrades and roll-backs, reproducible - build environments, unprivileged package management, and - per-user profiles. It uses low-level mechanisms from the " - (a (@ (href "https://nixos.org/nix/")) "Nix") - " package manager, but packages are " - (a (@ (href ,(manual-url "Defining-Packages.html"))) "defined") - " as native " - (a (@ (href ,(gnu-url "software/guile"))) "Guile") - " modules, using extensions to the " - (a (@ (href "http://schemers.org")) "Scheme") - " language=E2=80=94which makes it nicely hackable.") + ,(G_ + `(p + "GNU Guix provides " + ,(G_ `(a (@ (href ,(manual-url "Features.html"))) + "state-of-the-art package management features")) + " such as transactional upgrades and roll-backs, reproducible + build environments, unprivileged package management, and + per-user profiles. It uses low-level mechanisms from the " + ,(G_ `(a (@ (href "https://nixos.org/nix/")) "Nix")) + " package manager, but packages are " + ,(G_ `(a (@ (href ,(manual-url "Defining-Packages.html"))) "de= fined")) + " as native " + ,(G_ `(a (@ (href ,(gnu-url "software/guile"))) "Guile")) + " modules, using extensions to the " + ,(G_ `(a (@ (href "http://schemers.org")) "Scheme")) + " language=E2=80=94which makes it nicely hackable.")) =20 - (p - "Guix takes that a step further by additionally supporting statel= ess, - reproducible " - (a (@ (href ,(manual-url "Using-the-Configuration-System.html"))) - "operating system configurations") - ". This time the whole system is hackable in Scheme, from the " - (a (@ (href ,(manual-url "Initial-RAM-Disk.html"))) - "initial RAM disk") - " to the " - (a (@ (href ,(gnu-url "software/shepherd"))) - "initialization system") - ", and to the " - (a (@ (href ,(manual-url "Defining-Services.html"))) - "system services") - ".") + ,(G_ + `(p + "Guix takes that a step further by additionally supporting sta= teless, + reproducible " + ,(G_ `(a (@ (href ,(manual-url "Using-the-Configuration-System= .html"))) + "operating system configurations")) + ". This time the whole system is hackable in Scheme, from the = " + ,(G_ `(a (@ (href ,(manual-url "Initial-RAM-Disk.html"))) + "initial RAM disk")) + " to the " + ,(G_ `(a (@ (href ,(gnu-url "software/shepherd"))) + "initialization system")) + ", and to the " + ,(G_ `(a (@ (href ,(manual-url "Defining-Services.html"))) + "system services")) + ".")) =20 =20 - (h3 (@ (id "mantainer")) "Maintainer") + ,(G_ `(h3 (@ (id "mantainer")) "Maintainer")) =20 - (p - "Guix is currently maintained by Ludovic Court=C3=A8s and Ricardo - Wurmus. Please use the " - (a (@ (href ,(guix-url "contact/"))) "mailing lists") - " for contact. ") + ,(G_ + `(p + "Guix is currently maintained by Ludovic Court=C3=A8s and Rica= rdo + Wurmus. Please use the " + ,(G_ `(a (@ (href ,(guix-url "contact/"))) "mailing lists")) + " for contact. ")) =20 =20 - (h3 (@ (id "license")) "Licensing") + ,(G_ `(h3 (@ (id "license")) "Licensing")) =20 - (p - "Guix is free software; you can redistribute it and/or modify - it under the terms of the " - (a (@ (rel "license") (href ,(gnu-url "licenses/gpl.html"))) - "GNU General Public License") - " as published by the Free Software Foundation; either - version\xa03 of the License, or (at your option) any later - version. "))))) + ,(G_ + `(p + "Guix is free software; you can redistribute it and/or modify + it under the terms of the " + ,(G_ `(a (@ (rel "license") (href ,(gnu-url "licenses/gpl.html= "))) + "GNU General Public License")) + " as published by the Free Software Foundation; either + version\xa03 of the License, or (at your option) any later + version. ")))))) diff --git a/website/apps/base/templates/components.scm b/website/apps/ba= se/templates/components.scm index d3f6af1..666abec 100644 --- a/website/apps/base/templates/components.scm +++ b/website/apps/base/templates/components.scm @@ -12,6 +12,7 @@ #:use-module (apps aux web) #:use-module (apps base types) #:use-module (apps base utils) + #:use-module (apps i18n) #:use-module (srfi srfi-1) #:use-module (ice-9 match) #:export (breadcrumbs @@ -41,9 +42,9 @@ (apps base types)." `(nav (@ (class "breadcrumbs")) - (h2 (@ (class "a11y-offset")) "Your location:") + ,(G_ `(h2 (@ (class "a11y-offset")) "Your location:")) =20 - (a (@ (class "crumb") (href ,(guix-url))) "Home") (span " =E2=86=92 = ") + ,(G_ `(a (@ (class "crumb") (href ,(guix-url))) "Home")) (span " =E2= =86=92 ") ,@(separate (crumbs->shtml crumbs) '(span " =E2=86=92 ")))) =20 =20 @@ -121,8 +122,9 @@ (sxml->string* (match (contact-description contact) ((and multilingual (((? string?) (? string?)) ...)) - (match (assoc "en" multilingual) - (("en" blurb) blurb))) + (let ((code (car (string-split (%current-lingua) #\_)))) + (match (assoc code multilingual) + ((code blurb) blurb)))) (blurb blurb))) 30) @@ -145,7 +147,7 @@ ,(if (string=3D? (contact-log contact) "") "" `(small - " (" (a (@ (href ,(contact-log contact))) "archive") ") ")) + " (" ,(G_ `(a (@ (href ,(contact-log contact))) "archive")) ") ")) =20 ;; The description can be a list of language/blurb pairs. ,(match (contact-description contact) @@ -284,26 +286,26 @@ (h1 (a (@ (class "branding") (href ,(guix-url))) - (span (@ (class "a11y-offset")) "Guix"))) + ,(C_ "Website menu" `(span (@ (class "a11y-offset")) "Guix")))) =20 ;; Menu. (nav (@ (class "menu")) - (h2 (@ (class "a11y-offset")) "Website menu:") + ,(G_ `(h2 (@ (class "a11y-offset")) "Website menu:")) (ul - ,(menu-item #:label "Overview" #:active-item active-item #:url (gu= ix-url)) - ,(menu-item #:label "Download" #:active-item active-item #:url (gu= ix-url "download/")) - ,(menu-item #:label "Packages" #:active-item active-item #:url (gu= ix-url "packages/")) - ,(menu-item #:label "Blog" #:active-item active-item #:url (guix-u= rl "blog/")) - ,(menu-item #:label "Help" #:active-item active-item #:url (guix-u= rl "help/")) - ,(menu-item #:label "Donate" #:active-item active-item #:url (guix= -url "donate/")) - - ,(menu-dropdown #:label "About" #:active-item active-item #:url (g= uix-url "about/") + ,(C_ "Website menu" (menu-item #:label "Overview" #:active-item ac= tive-item #:url (guix-url))) + ,(C_ "Website menu" (menu-item #:label "Download" #:active-item ac= tive-item #:url (guix-url "download/"))) + ,(C_ "Website menu" (menu-item #:label "Packages" #:active-item ac= tive-item #:url (guix-url "packages/"))) + ,(C_ "Website menu" (menu-item #:label "Blog" #:active-item active= -item #:url (guix-url "blog/"))) + ,(C_ "Website menu" (menu-item #:label "Help" #:active-item active= -item #:url (guix-url "help/"))) + ,(C_ "Website menu" (menu-item #:label "Donate" #:active-item acti= ve-item #:url (guix-url "donate/"))) + + ,(menu-dropdown #:label (C_ "Website menu" "About") #:active-item = active-item #:url (guix-url "about/") #:items - (list - (menu-item #:label "Contact" #:active-item active-item #:url (guix-url= "contact/")) - (menu-item #:label "Contribute" #:active-item active-item #:url (guix-= url "contribute/")) - (menu-item #:label "Security" #:active-item active-item #:url (guix-ur= l "security/")) - (menu-item #:label "Graphics" #:active-item active-item #:url (guix-ur= l "graphics/")))))) + (list + (C_ "Website menu" (menu-item #:label "Contact" #:active-item a= ctive-item #:url (guix-url "contact/"))) + (C_ "Website menu" (menu-item #:label "Contribute" #:active-ite= m active-item #:url (guix-url "contribute/"))) + (C_ "Website menu" (menu-item #:label "Security" #:active-item = active-item #:url (guix-url "security/"))) + (C_ "Website menu" (menu-item #:label "Graphics" #:active-item = active-item #:url (guix-url "graphics/"))))))) =20 ;; Menu button. (a @@ -321,10 +323,10 @@ TOTAL-PAGES (number) The total number of pages that should be displayed." (if (> total-pages 1) - `(span - (@ (class "page-number-indicator")) - " (Page " ,(number->string page-number) - " of " ,(number->string total-pages) ")") + (G_ `(span + (@ (class "page-number-indicator")) + " (Page " ,(number->string page-number) + " of " ,(number->string total-pages) ")")) "")) =20 =20 @@ -345,8 +347,8 @@ (@ (class "page-selector")) (h3 (@ (class "a11y-offset")) - ,(string-append "Page " (number->string active-page) " of " - (number->string pages) ". Go to another page: ")) + ,(G_ (string-append "Page " (number->string active-page) " of " + (number->string pages) ". Go to another page: "= ))) ,(if (> pages 1) (map (lambda (page-number) diff --git a/website/apps/base/templates/contact.scm b/website/apps/base/= templates/contact.scm index d4ee2f2..5b8df63 100644 --- a/website/apps/base/templates/contact.scm +++ b/website/apps/base/templates/contact.scm @@ -7,31 +7,33 @@ #:use-module (apps base templates theme) #:use-module (apps base types) #:use-module (apps base utils) + #:use-module (apps i18n) #:export (contact-t)) =20 =20 (define (contact-t context) "Return the Contact page in SHTML with the data in CONTEXT." (theme - #:title '("Contact") + #:title (G_ '("Contact")) #:description - "A list of channels to communicate with GNU=C2=A0Guix users - and developers about anything you want." + (G_ "A list of channels to communicate with GNU=C2=A0Guix users + and developers about anything you want.") #:keywords - '("GNU" "Linux" "Unix" "Free software" "Libre software" - "Operating system" "GNU Hurd" "GNU Guix package manager" - "Community" "Mailing lists" "IRC channels" "Bug reports" "Help") - #:active-menu-item "About" + (string-split ;TRANSLATORS: |-separated list of webpage keywords + (G_ "GNU|Linux|Unix|Free software|Libre software|Operating \ +system|GNU Hurd|GNU Guix package manager|Community|Mailing lists|IRC +channels|Bug reports|Help") #\|) + #:active-menu-item (C_ "Website menu" "About") #:css (list (guix-url "static/base/css/page.css") (guix-url "static/base/css/buttons.css") (guix-url "static/base/css/contact.css")) - #:crumbs (list (crumb "Contact" "./")) + #:crumbs (list (crumb (C_ "Website menu" "Contact") "./")) #:content `(main (section (@ (class "page centered-block limit-width")) - (h2 "Contact") + ,(G_ `(h2 "Contact")) =20 ,@(map contact->shtml diff --git a/website/apps/base/templates/home.scm b/website/apps/base/tem= plates/home.scm index 5cb3bf5..827724f 100644 --- a/website/apps/base/templates/home.scm +++ b/website/apps/base/templates/home.scm @@ -8,24 +8,26 @@ #:use-module (apps base types) #:use-module (apps base utils) #:use-module (apps blog templates components) + #:use-module (apps i18n) #:export (home-t)) =20 =20 (define (home-t context) "Return the Home page in SHTML using the data in CONTEXT." (theme - #:title '("GNU's advanced distro and transactional package manager") + #:title (G_ '("GNU's advanced distro and transactional package manage= r")) #:description - "Guix is an advanced distribution of the GNU operating system. + (G_ "Guix is an advanced distribution of the GNU operating system. Guix is technology that respects the freedom of computer users. - You are free to run the system for any purpose, study how it works, - improve it, and share it with the whole world." + You are free to run the system for any purpose, study how it + works, improve it, and share it with the whole world.") #:keywords - '("GNU" "Linux" "Unix" "Free software" "Libre software" - "Operating system" "GNU Hurd" "GNU Guix package manager" - "GNU Guile" "Guile Scheme" "Transactional upgrades" - "Functional package management" "Reproducibility") - #:active-menu-item "Overview" + (string-split ;TRANSLATORS: |-separated list of webpage keywords + (G_ "GNU|Linux|Unix|Free software|Libre software|Operating \ +system|GNU Hurd|GNU Guix package manager|GNU Guile|Guile \ +Scheme|Transactional upgrades|Functional package \ +management|Reproducibility") #\|) + #:active-menu-item (C_ "Website menu" "Overview") #:css (list (guix-url "static/base/css/item-preview.css") (guix-url "static/base/css/index.css")) @@ -34,83 +36,89 @@ ;; Featured content. (section (@ (class "featured-content")) - (h2 (@ (class "a11y-offset")) "Summary") + ,(G_ `(h2 (@ (class "a11y-offset")) "Summary")) (ul - (li - (b "Liberating.") - " Guix is an advanced - distribution of the " - ,(link-yellow - #:label "GNU operating system" - #:url (gnu-url "gnu/about-gnu.html")) - " developed by the " - ,(link-yellow - #:label "GNU Project" - #:url (gnu-url)) - "=E2=80=94which respects the " - ,(link-yellow - #:label "freedom of computer users" - #:url (gnu-url "distros/free-system-distribution-guidelines.html")) - ". ") - - (li - (b "Dependable.") - " Guix " - ,(link-yellow - #:label "supports" - #:url (manual-url "Package-Management.html")) - " transactional upgrades and roll-backs, unprivileged - package management, " - ,(link-yellow - #:label "and more" - #:url (manual-url "Features.html")) - ". When used as a standalone distribution, Guix supports " - ,(link-yellow - #:label "declarative system configuration" - #:url (manual-url "Using-the-Configuration-System.html")) - " for transparent and reproducible operating systems.") - - (li - (b "Hackable.") - " It provides " - ,(link-yellow - #:label "Guile Scheme" - #:url (gnu-url "software/guile/")) - " APIs, including high-level embedded domain-specific - languages (EDSLs) to " - ,(link-yellow - #:label "define packages" - #:url (manual-url "Defining-Packages.html")) - " and " - ,(link-yellow - #:label "whole-system configurations" - #:url (manual-url "System-Configuration.html")) - ".")) + ,(G_ + `(li + ,(G_ `(b "Liberating.")) + " Guix is an advanced distribution of the " + ,(G_ (link-yellow + #:label "GNU operating system" + #:url (gnu-url "gnu/about-gnu.html"))) + " developed by the " + ,(G_ (link-yellow + #:label "GNU Project" + #:url (gnu-url))) + "=E2=80=94which respects the " + ,(G_ (link-yellow + #:label "freedom of computer users" + #:url (gnu-url "distros/free-system-distribution-\ +guidelines.html"))) + ". ")) + + ,(G_ + `(li + ,(G_ `(b "Dependable.")) + " Guix " + ,(G_ (link-yellow + #:label "supports" + #:url (manual-url "Package-Management.html"))) + " transactional upgrades and roll-backs, unprivileged \ +package management, " + ,(G_ (link-yellow + #:label "and more" + #:url (manual-url "Features.html"))) + ". When used as a standalone distribution, Guix supports " + ,(G_ (link-yellow + #:label "declarative system configuration" + #:url (manual-url "Using-the-Configuration-System.html"= ))) + " for transparent and reproducible operating systems.")) + + ,(G_ + `(li + ,(G_ `(b "Hackable.")) + " It provides " + ,(G_ (link-yellow + #:label "Guile Scheme" + #:url (gnu-url "software/guile/"))) + " APIs, including high-level embedded domain-specific \ +languages (EDSLs) to " + ,(G_ (link-yellow + #:label "define packages" + #:url (manual-url "Defining-Packages.html"))) + " and " + ,(G_ (link-yellow + #:label "whole-system configurations" + #:url (manual-url "System-Configuration.html"))) + "."))) =20 (div (@ (class "action-box centered-text")) ,(button-big - #:label (string-append "DOWNLOAD v" (latest-guix-version)) + #:label (apply string-append + (C_ "button" `("DOWNLOAD v" ,(latest-guix-versio= n) ""))) #:url (guix-url "download/") #:light #true) " " ; A space for readability in non-CSS browsers. ,(button-big - #:label "CONTRIBUTE" + #:label (C_ "button" "CONTRIBUTE") #:url (guix-url "contribute/") #:light #true))) =20 ;; Discover Guix. (section (@ (class "discovery-box")) - (h2 "Discover Guix") - - (p - (@ (class "limit-width centered-block")) - "Guix comes with thousands of packages which include - applications, system tools, documentation, fonts, and other - digital goods readily available for installing with the " - ,(link-yellow #:label "GNU Guix" #:url "#guix-in-other-distros") - " package manager.") + ,(G_ `(h2 "Discover Guix")) + + ,(G_ + `(p + (@ (class "limit-width centered-block")) + "Guix comes with thousands of packages which include \ +applications, system tools, documentation, fonts, and other digital \ +goods readily available for installing with the " + ,(G_ (link-yellow #:label "GNU Guix" + #:url (identity "#guix-in-other-distros"))) + " package manager.")) =20 (div (@ (class "screenshots-box")) @@ -119,55 +127,57 @@ (div (@ (class "action-box centered-text")) ,(button-big - #:label "ALL PACKAGES" + #:label (C_ "button" "ALL PACKAGES") #:url (guix-url "packages/") #:light #true)) =20 ,(horizontal-separator #:light #true) =20 ;; Guix in different fields. - (h3 "GNU Guix in your field") + ,(G_ `(h3 "GNU Guix in your field")) =20 - (p - (@ (class "limit-width centered-block")) - "Read some stories about how people are using GNU=C2=A0Guix in th= eir daily - lives.") + ,(G_ + `(p + (@ (class "limit-width centered-block")) + "Read some stories about how people are using GNU=C2=A0Guix in +their daily lives.")) =20 (div (@ (class "fields-box")) =20 " " ; A space for readability in non-CSS browsers (same below). ,(button-big - #:label "SOFTWARE DEVELOPMENT" - #:url (guix-url "blog/tags/software-development/") - #:light #true) + #:label (C_ "button" "SOFTWARE DEVELOPMENT") + #:url (guix-url "blog/tags/software-development/") + #:light #true) " " ,(button-big - #:label "BIOINFORMATICS" - #:url (guix-url "blog/tags/bioinformatics/") - #:light #true) + #:label (C_ "button" "BIOINFORMATICS") + #:url (guix-url "blog/tags/bioinformatics/") + #:light #true) " " ,(button-big - #:label "HIGH PERFORMANCE COMPUTING" - #:url (guix-url "blog/tags/high-performance-computing/") - #:light #true) + #:label (C_ "button" "HIGH PERFORMANCE COMPUTING") + #:url (guix-url "blog/tags/high-performance-computing/") + #:light #true) " " ,(button-big - #:label "RESEARCH" - #:url (guix-url "blog/tags/research/") - #:light #true) + #:label (C_ "button" "RESEARCH") + #:url (guix-url "blog/tags/research/") + #:light #true) " " ,(button-big - #:label "ALL FIELDS..." - #:url (guix-url "blog/") - #:light #true)) + #:label (C_ "button" "ALL FIELDS...") + #:url (guix-url "blog/") + #:light #true)) =20 ,(horizontal-separator #:light #true) =20 ;; Using Guix in other distros. - (h3 - (@ (id "guix-in-other-distros")) - "GNU Guix in other GNU/Linux distros") + ,(G_ + `(h3 + (@ (id "guix-in-other-distros")) + "GNU Guix in other GNU/Linux distros")) =20 (div (@ (class "info-box")) @@ -176,54 +186,55 @@ (src "https://audio-video.gnu.org/video/misc/2016-07__GNU_Guix_Demo_= 2.webm") (poster ,(guix-url "static/media/img/guix-demo.png")) (controls "controls")) - (p - "Video: " - ,(link-yellow - #:label "Demo of Guix in another GNU/Linux distribution" - #:url "https://audio-video.gnu.org/video/misc/2016-07__GNU_Guix_Demo= _2.webm") - " (1 minute, 30 seconds)."))) + ,(G_ + `(p + "Video: " + ,(G_ (link-yellow + #:label "Demo of Guix in another GNU/Linux distributio= n" + #:url "https://audio-video.gnu.org/video/misc/\ +2016-07__GNU_Guix_Demo_2.webm")) + " (1 minute, 30 seconds).")))) =20 (div (@ (class "info-box justify-left")) - (p - "If you don't use GNU=C2=A0Guix as a standalone GNU/Linux distribution, - you still can use it as a - package manager on top of any GNU/Linux distribution. This - way, you can benefit from all its conveniences.") + ,(G_ `(p + "If you don't use GNU=C2=A0Guix as a standalone GNU/Linux = \ +distribution, you still can use it as a package manager on top of any \ +GNU/Linux distribution. This way, you can benefit from all its convenien= ces.")) =20 - (p - "Guix won't interfere with the package manager that comes - with your distribution. They can live together.")) + ,(G_ `(p + "Guix won't interfere with the package manager that comes = \ +with your distribution. They can live together."))) =20 (div (@ (class "action-box centered-text")) ,(button-big - #:label "TRY IT OUT!" + #:label (C_ "button" "TRY IT OUT!") #:url (guix-url "download/") #:light #true))) =20 ;; Latest Blog posts. (section (@ (class "centered-text")) - (h2 "Blog") + ,(G_ `(h2 "Blog")) =20 ,@(map post-preview (context-datum context "posts")) =20 (div (@ (class "action-box centered-text")) ,(button-big - #:label "ALL POSTS" + #:label (C_ "button" "ALL POSTS") #:url (guix-url "blog/")))) =20 ;; Contact info. (section (@ (class "contact-box centered-text")) - (h2 "Contact") + ,(G_ `(h2 "Contact")) =20 ,@(map contact-preview (context-datum context "contact-media")) =20 (div (@ (class "action-box centered-text")) ,(button-big - #:label "ALL CONTACT MEDIA" + #:label (C_ "button" "ALL CONTACT MEDIA") #:url (guix-url "contact/"))))))) diff --git a/website/apps/base/templates/theme.scm b/website/apps/base/te= mplates/theme.scm index ecb27ef..f300a8c 100644 --- a/website/apps/base/templates/theme.scm +++ b/website/apps/base/templates/theme.scm @@ -5,11 +5,12 @@ (define-module (apps base templates theme) #:use-module (apps base templates components) #:use-module (apps base utils) + #:use-module (apps i18n) #:export (theme)) =20 =20 (define* (theme #:key - (lang-tag "en") + (lang-tag (car (string-split (%current-lingua) #\_))) (title '()) (description "") (keywords '()) @@ -65,12 +66,14 @@ `((doctype "html") =20 (html - (@ (lang "en")) + (@ (lang ,(car (string-split (%current-lingua) #\_)))) =20 (head ,(if (null? title) - `(title "GNU=C2=A0Guix") - `(title ,(string-join (append title '("GNU=C2=A0Guix")) " =E2=80=94 = "))) + `(title (C_ "webpage title" "GNU=C2=A0Guix")) + `(title ,(string-join (append title + (C_ "webpage title" '("GNU=C2=A0= Guix"))) + " =E2=80=94 "))) (meta (@ (charset "UTF-8"))) (meta (@ (name "keywords") (content ,(string-join keywords ", ")))= ) (meta (@ (name "description") (content ,description))) @@ -91,7 +94,7 @@ css) ;; Feeds. (link (@ (type "application/atom+xml") (rel "alternate") - (title "GNU=C2=A0Guix =E2=80=94 Activity Feed") + (title (C_ "webpage title" "GNU=C2=A0Guix =E2=80=94 Activity Fee= d")) (href ,(guix-url "feeds/blog.atom")))) (link (@ (rel "icon") (type "image/png") (href ,(guix-url "static/base/img/icon.png")))) @@ -108,17 +111,22 @@ ,(if (null? crumbs) "" (breadcrumbs crumbs)) =20 ,content - (footer - "Made with " (span (@ (class "metta")) "=E2=99=A5") - " by humans and powered by " - (a (@ (class "link-yellow") (href ,(gnu-url "software/guile/"))) - "GNU Guile") ". " - (a - (@ (class "link-yellow") - (href "//git.savannah.gnu.org/cgit/guix/guix-artwork.git/tree/web= site")) - "Source code") - " under the " - (a - (@ (class "link-yellow") - (href ,(gnu-url "licenses/agpl-3.0.html"))) - "GNU AGPL") "."))))) + ,(G_ + `(footer + "Made with " ,(G_ `(span (@ (class "metta")) "=E2=99=A5")) + " by humans and powered by " + ,(G_ `(a + (@ (class "link-yellow") + (href ,(gnu-url "software/guile/"))) + "GNU Guile")) + ". " + ,(G_ `(a + (@ (class "link-yellow") + (href "//git.savannah.gnu.org/cgit/guix/guix-artwork= .git/tree/website")) + "Source code")) + " under the " + ,(G_ `(a + (@ (class "link-yellow") + (href ,(gnu-url "licenses/agpl-3.0.html"))) + "GNU AGPL")) + ".")))))) --=20 2.22.0 --cyuyic77bivek3sb Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="0005-website-Generate-localizeable-POT-file.patch" Content-Transfer-Encoding: quoted-printable >From 13f7a039e6d04e5b2b74cb2cf23fd837dbcfaffa Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Thu, 8 Aug 2019 00:04:04 +0200 Subject: [PATCH 5/6] website: Generate localizeable POT file. * website/po/guix-website.pot: Add it. --- website/po/guix-website.pot | 294 ++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 website/po/guix-website.pot diff --git a/website/po/guix-website.pot b/website/po/guix-website.pot new file mode 100644 index 0000000..d2e0e74 --- /dev/null +++ b/website/po/guix-website.pot @@ -0,0 +1,294 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Ludovic Court=C3=A8s +# This file is distributed under the same license as the guix-website pa= ckage. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: guix-website\n" +"Report-Msgid-Bugs-To: ludo@gnu.org\n" +"POT-Creation-Date: 2019-08-07 23:03+0200\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=3DUTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: apps/base/templates/about.scm:16 +msgid "About" +msgstr "" + +#: apps/base/templates/about.scm:18 apps/base/templates/home.scm:20 +msgid "Guix is an advanced distribution of the GNU operating system.\n = Guix is technology that respects the freedom of computer users.\n You = are free to run the system for any purpose, study how it\n works, impro= ve it, and share it with the whole world." +msgstr "" + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/about.scm:24 +msgid "GNU|Linux|Unix|Free software|Libre software|Operating system|GNU = Hurd|GNU Guix package manager" +msgstr "" + +#: apps/base/templates/about.scm:26 apps/base/templates/about.scm:29 app= s/base/templates/components.scm:302 apps/base/templates/contact.scm:26 +msgctxt "Website menu" +msgid "About" +msgstr "" + +#: apps/base/templates/about.scm:34 +msgid "About the Project" +msgstr "" + +#: apps/base/templates/about.scm:36 +msgid "The <1>GNU Guix package and system manager is a <2>free softw= are project developed by volunteers around the world under the\n = umbrella of the <3>GNU Project. " +msgstr "" + +#: apps/base/templates/about.scm:45 +msgid "Guix System is an advanced distribution of the <1>GNU operating s= ystem. It uses the <2>Linux-libre kernel, and support for <3>the= Hurd is being worked on. As a GNU distribution, it is committed\n = to respecting and enhancing <4>the freedom of its users. A= s such, it adheres to the <5>GNU Free System Distribution Guidelines.= " +msgstr "" + +#: apps/base/templates/about.scm:61 +msgid "GNU Guix provides <1>state-of-the-art package management features= such as transactional upgrades and roll-backs, reproducible\n = build environments, unprivileged package management, and\n = per-user profiles. It uses low-level mechanisms from the <2>Nix pa= ckage manager, but packages are <3>defined as native <4>Guile mod= ules, using extensions to the <5>Scheme language=E2=80=94which makes = it nicely hackable." +msgstr "" + +#: apps/base/templates/about.scm:78 +msgid "Guix takes that a step further by additionally supporting statele= ss,\n reproducible <1>operating system configurations. This= time the whole system is hackable in Scheme, from the <2>initial RAM dis= k to the <3>initialization system, and to the <4>system services<= /4>." +msgstr "" + +#: apps/base/templates/about.scm:96 +msgid "Maintainer" +msgstr "" + +#: apps/base/templates/about.scm:98 +msgid "Guix is currently maintained by Ludovic Court=C3=A8s and Ricardo\= n Wurmus. Please use the <1>mailing lists for contact. " +msgstr "" + +#: apps/base/templates/about.scm:106 +msgid "Licensing" +msgstr "" + +#: apps/base/templates/about.scm:108 +msgid "Guix is free software; you can redistribute it and/or modify\n = it under the terms of the <1>GNU General Public License as pub= lished by the Free Software Foundation; either\n version=C2=A03 = of the License, or (at your option) any later\n version. " +msgstr "" + +#: apps/base/templates/components.scm:45 +msgid "Your location:" +msgstr "" + +#: apps/base/templates/components.scm:47 +msgid "Home" +msgstr "" + +#: apps/base/templates/components.scm:150 +msgid "archive" +msgstr "" + +#: apps/base/templates/components.scm:289 +msgctxt "Website menu" +msgid "Guix" +msgstr "" + +#: apps/base/templates/components.scm:293 +msgid "Website menu:" +msgstr "" + +#: apps/base/templates/components.scm:295 apps/base/templates/home.scm:3= 0 +msgctxt "Website menu" +msgid "Overview" +msgstr "" + +#: apps/base/templates/components.scm:296 +msgctxt "Website menu" +msgid "Download" +msgstr "" + +#: apps/base/templates/components.scm:297 +msgctxt "Website menu" +msgid "Packages" +msgstr "" + +#: apps/base/templates/components.scm:298 +msgctxt "Website menu" +msgid "Blog" +msgstr "" + +#: apps/base/templates/components.scm:299 +msgctxt "Website menu" +msgid "Help" +msgstr "" + +#: apps/base/templates/components.scm:300 +msgctxt "Website menu" +msgid "Donate" +msgstr "" + +#: apps/base/templates/components.scm:305 apps/base/templates/contact.sc= m:31 +msgctxt "Website menu" +msgid "Contact" +msgstr "" + +#: apps/base/templates/components.scm:306 +msgctxt "Website menu" +msgid "Contribute" +msgstr "" + +#: apps/base/templates/components.scm:307 +msgctxt "Website menu" +msgid "Security" +msgstr "" + +#: apps/base/templates/components.scm:308 +msgctxt "Website menu" +msgid "Graphics" +msgstr "" + +#: apps/base/templates/components.scm:326 +msgid " (Page <1/> of <2/>)" +msgstr "" + +#: apps/base/templates/components.scm:350 +msgid "Page <1/> of <2/>. Go to another page: " +msgstr "" + +#: apps/base/templates/contact.scm:17 apps/base/templates/contact.scm:36= apps/base/templates/home.scm:232 +msgid "Contact" +msgstr "" + +#: apps/base/templates/contact.scm:19 +msgid "A list of channels to communicate with GNU=C2=A0Guix users\n an= d developers about anything you want." +msgstr "" + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/contact.scm:23 +msgid "GNU|Linux|Unix|Free software|Libre software|Operating system|GNU = Hurd|GNU Guix package manager|Community|Mailing lists|IRC\nchannels|Bug r= eports|Help" +msgstr "" + +#: apps/base/templates/home.scm:18 +msgid "GNU's advanced distro and transactional package manager" +msgstr "" + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/home.scm:26 +msgid "GNU|Linux|Unix|Free software|Libre software|Operating system|GNU = Hurd|GNU Guix package manager|GNU Guile|Guile Scheme|Transactional upgrad= es|Functional package management|Reproducibility" +msgstr "" + +#: apps/base/templates/home.scm:39 +msgid "Summary" +msgstr "" + +#: apps/base/templates/home.scm:41 +msgid "<1>Liberating. Guix is an advanced distribution of the <2>GNU= operating system developed by the <3>GNU Project=E2=80=94which r= espects the <4>freedom of computer users. " +msgstr "" + +#: apps/base/templates/home.scm:59 +msgid "<1>Dependable. Guix <2>supports transactional upgrades an= d roll-backs, unprivileged package management, <3>and more. When use= d as a standalone distribution, Guix supports <4>declarative system confi= guration for transparent and reproducible operating systems." +msgstr "" + +#: apps/base/templates/home.scm:77 +msgid "<1>Hackable. It provides <2>Guile Scheme APIs, including = high-level embedded domain-specific languages (EDSLs) to <3>define packag= es and <4>whole-system configurations." +msgstr "" + +#: apps/base/templates/home.scm:99 +msgctxt "button" +msgid "DOWNLOAD v<1/>" +msgstr "" + +#: apps/base/templates/home.scm:104 +msgctxt "button" +msgid "CONTRIBUTE" +msgstr "" + +#: apps/base/templates/home.scm:111 +msgid "Discover Guix" +msgstr "" + +#: apps/base/templates/home.scm:113 +msgid "Guix comes with thousands of packages which include applications,= system tools, documentation, fonts, and other digital goods readily avai= lable for installing with the <1>GNU Guix package manager." +msgstr "" + +#: apps/base/templates/home.scm:130 +msgctxt "button" +msgid "ALL PACKAGES" +msgstr "" + +#: apps/base/templates/home.scm:137 +msgid "GNU Guix in your field" +msgstr "" + +#: apps/base/templates/home.scm:139 +msgid "Read some stories about how people are using GNU=C2=A0Guix in\nth= eir daily lives." +msgstr "" + +#: apps/base/templates/home.scm:150 +msgctxt "button" +msgid "SOFTWARE DEVELOPMENT" +msgstr "" + +#: apps/base/templates/home.scm:155 +msgctxt "button" +msgid "BIOINFORMATICS" +msgstr "" + +#: apps/base/templates/home.scm:160 +msgctxt "button" +msgid "HIGH PERFORMANCE COMPUTING" +msgstr "" + +#: apps/base/templates/home.scm:165 +msgctxt "button" +msgid "RESEARCH" +msgstr "" + +#: apps/base/templates/home.scm:170 +msgctxt "button" +msgid "ALL FIELDS..." +msgstr "" + +#: apps/base/templates/home.scm:177 +msgid "GNU Guix in other GNU/Linux distros" +msgstr "" + +#: apps/base/templates/home.scm:189 +msgid "Video: <1>Demo of Guix in another GNU/Linux distribution<1.1/>htt= ps://audio-video.gnu.org/video/misc/2016-07__GNU_Guix_Demo_2.webm (1 = minute, 30 seconds)." +msgstr "" + +#: apps/base/templates/home.scm:200 +msgid "If you don't use GNU=C2=A0Guix as a standalone GNU/Linux distribu= tion, you still can use it as a package manager on top of any GNU/Linux d= istribution. This way, you can benefit from all its conveniences." +msgstr "" + +#: apps/base/templates/home.scm:205 +msgid "Guix won't interfere with the package manager that comes with you= r distribution. They can live together." +msgstr "" + +#: apps/base/templates/home.scm:212 +msgctxt "button" +msgid "TRY IT OUT!" +msgstr "" + +#: apps/base/templates/home.scm:219 +msgid "Blog" +msgstr "" + +#: apps/base/templates/home.scm:226 +msgctxt "button" +msgid "ALL POSTS" +msgstr "" + +#: apps/base/templates/home.scm:239 +msgctxt "button" +msgid "ALL CONTACT MEDIA" +msgstr "" + +#: apps/base/templates/theme.scm:73 apps/base/templates/theme.scm:75 +msgctxt "webpage title" +msgid "GNU=C2=A0Guix" +msgstr "" + +#: apps/base/templates/theme.scm:97 +msgctxt "webpage title" +msgid "GNU=C2=A0Guix =E2=80=94 Activity Feed" +msgstr "" + +#: apps/base/templates/theme.scm:114 +msgid "Made with <1>=E2=99=A5 by humans and powered by <2>GNU Guile<= /2>. <3>Source code under the <4>GNU AGPL." +msgstr "" --=20 2.22.0 --cyuyic77bivek3sb Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="0006-website-Add-German-translation.patch" Content-Transfer-Encoding: quoted-printable >From b8e73093b194b894e02bc8ae16b8efb3182a2a65 Mon Sep 17 00:00:00 2001 From: Florian Pelz Date: Thu, 8 Aug 2019 00:08:47 +0200 Subject: [PATCH 6/6] website: Add German translation. * website/po/de.po: New file. * website/po/LINGUAS: Add de_DE lingua. --- website/po/LINGUAS | 1 + website/po/de.po | 439 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 website/po/de.po diff --git a/website/po/LINGUAS b/website/po/LINGUAS index 7741b83..782116d 100644 --- a/website/po/LINGUAS +++ b/website/po/LINGUAS @@ -1 +1,2 @@ +de_DE en_US diff --git a/website/po/de.po b/website/po/de.po new file mode 100644 index 0000000..2770884 --- /dev/null +++ b/website/po/de.po @@ -0,0 +1,439 @@ +# German translations for guix-website package. +# Copyright (C) 2019 Ludovic Court=C3=A8s +# This file is distributed under the same license as the guix-website pa= ckage. +# Florian Pelz , 2019. +# +msgid "" +msgstr "" +"Project-Id-Version: guix-website\n" +"Report-Msgid-Bugs-To: ludo@gnu.org\n" +"POT-Creation-Date: 2019-08-07 23:03+0200\n" +"PO-Revision-Date: 2019-08-07 22:37+0200\n" +"Last-Translator: Florian Pelz \n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=3DUTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3D2; plural=3D(n !=3D 1);\n" + +#: apps/base/templates/about.scm:16 +msgid "About" +msgstr "=C3=9Cber Guix" + +#: apps/base/templates/about.scm:18 apps/base/templates/home.scm:20 +msgid "" +"Guix is an advanced distribution of the GNU operating system.\n" +" Guix is technology that respects the freedom of computer users.\n" +" You are free to run the system for any purpose, study how it\n" +" works, improve it, and share it with the whole world." +msgstr "" +"Guix ist eine fortgeschrittene Distribution des GNU-Betriebssystems.\n" +" Guix ist eine Technologie, die die Freiheit der Benutzer von " +"Rechenger=C3=A4ten respektiert.\n" +" Es steht Ihnen frei, das System zu jedem Zweck auszuf=C3=BChren, sei= ne " +"Funktionsweise zu studieren,\n" +" es zu verbessern, und es mit der ganzen Welt zu teilen." + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/about.scm:24 +msgid "" +"GNU|Linux|Unix|Free software|Libre software|Operating system|GNU Hurd|G= NU " +"Guix package manager" +msgstr "" +"GNU|Linux|Unix|Freie Software|Libre-Software|Betriebssystem|GNU Hurd|GN= U-" +"Guix-Paketverwaltung" + +#: apps/base/templates/about.scm:26 apps/base/templates/about.scm:29 +#: apps/base/templates/components.scm:302 apps/base/templates/contact.sc= m:26 +msgctxt "Website menu" +msgid "About" +msgstr "=C3=9Cber Guix" + +#: apps/base/templates/about.scm:34 +msgid "About the Project" +msgstr "=C3=9Cber das Projekt" + +#: apps/base/templates/about.scm:36 +msgid "" +"The <1>GNU Guix package and system manager is a <2>free software " +"project developed by volunteers around the world under the\n" +" umbrella of the <3>GNU Project. " +msgstr "" +"<1>GNU Guix, ein Programm zur Verwaltung von Paketen und Systemen, = ist " +"ein <2>Freie-Software-Projekt, das von Freiwilligen aus der ganzen = Welt " +"im Rahmen des <3>GNU-Projekts entwickelt wird. " + +#: apps/base/templates/about.scm:45 +msgid "" +"Guix System is an advanced distribution of the <1>GNU operating system<= /1>. " +"It uses the <2>Linux-libre kernel, and support for <3>the Hurd = is " +"being worked on. As a GNU distribution, it is committed\n" +" to respecting and enhancing <4>the freedom of its users= . As " +"such, it adheres to the <5>GNU Free System Distribution Guidelines.= " +msgstr "" +"=E2=80=9EGuix System=E2=80=9C ist eine fortgeschrittene Distribution de= s <1>GNU-" +"Betriebssystems. Es verwendet <2>Linux-libre als seinen Kernel;= an " +"Unterst=C3=BCtzung f=C3=BCr <3>GNU Hurd wird gearbeitet. Als GNU-Di= stribution " +"geh=C3=B6rt es zu seiner Zielsetzung, <4>die Freiheit seiner Nutzer= zu " +"respektieren und zu vermehren. Daher folgt es den <5>Richtlinien f=C3=BC= r Freie " +"Systemdistributionen." + +#: apps/base/templates/about.scm:61 +msgid "" +"GNU Guix provides <1>state-of-the-art package management features s= uch " +"as transactional upgrades and roll-backs, reproducible\n" +" build environments, unprivileged package management, and\n" +" per-user profiles. It uses low-level mechanisms from the " +"<2>Nix package manager, but packages are <3>defined as native " +"<4>Guile modules, using extensions to the <5>Scheme language=E2= =80=94which " +"makes it nicely hackable." +msgstr "" +"GNU Guix bietet <1>Paketverwaltungsfunktionalit=C3=A4ten auf dem Stand = der " +"Technik, wie etwa transaktionelle Aktualisierungen und R=C3=BCckset= zungen, " +"reproduzierbare Erstellungsumgebungen, eine =E2=80=9Eunprivilegierte=E2= =80=9C " +"Paketverwaltung f=C3=BCr Nutzer ohne besondere Berechtigungen sowie ein= eigenes " +"Paketprofil f=C3=BCr jeden Nutzer. Dazu verwendet es dieselben Mechanis= men, die " +"dem Paketverwaltungsprogramm <2>Nix zu Grunde liegen, jedoch werden= " +"Pakete als reine <4>Guile-Module <3>definiert. Dazu erweitert G= uix " +"die <5>Scheme-Programmiersprache, wodurch es leicht ist, selbst an = " +"diesen zu hacken." + +#: apps/base/templates/about.scm:78 +msgid "" +"Guix takes that a step further by additionally supporting stateless,\n" +" reproducible <1>operating system configurations. This ti= me " +"the whole system is hackable in Scheme, from the <2>initial RAM disk to " +"the <3>initialization system, and to the <4>system services." +msgstr "" +"Guix geht dabei noch einen Schritt weiter, indem es zus=C3=A4tzlich noc= h " +"zustandslose, reproduzierbare <1>Betriebssystemkonfigurationen " +"unterst=C3=BCtzt. In diesem Fall kann am ganzen System in Scheme gehack= t werden, " +"von der <2>initialen RAM-Disk bis hin zum <3>Initialisierungssystem= " +"und den <4>Systemdiensten." + +#: apps/base/templates/about.scm:96 +msgid "Maintainer" +msgstr "Betreuer" + +#: apps/base/templates/about.scm:98 +msgid "" +"Guix is currently maintained by Ludovic Court=C3=A8s and Ricardo\n" +" Wurmus. Please use the <1>mailing lists for contact. " +msgstr "" +"Die Betreuer (=E2=80=9EMaintainer=E2=80=9C) von Guix sind zur Zeit Ludo= vic Court=C3=A8s und " +"Ricardo Wurmus. Benutzen Sie bitte die <1>Mailing-Listen, um Kontak= t " +"aufzunehmen." + +#: apps/base/templates/about.scm:106 +msgid "Licensing" +msgstr "Lizenzierung" + +#: apps/base/templates/about.scm:108 +msgid "" +"Guix is free software; you can redistribute it and/or modify\n" +" it under the terms of the <1>GNU General Public License a= s " +"published by the Free Software Foundation; either\n" +" version=C2=A03 of the License, or (at your option) any later\= n" +" version. " +msgstr "" +"Guix ist freie Software. Sie k=C3=B6nnen es weitergeben und/oder ver=C3= =A4ndern, " +"solange Sie sich an die Regeln der <1>GNU General Public License ha= lten, " +"so wie sie von der Free Software Foundation festgelegt wurden; entweder= in " +"Version=C2=A03 der Lizenz oder (nach Ihrem Ermessen) in jeder neueren V= ersion." + +#: apps/base/templates/components.scm:45 +msgid "Your location:" +msgstr "Sie befinden sich hier:" + +#: apps/base/templates/components.scm:47 +msgid "Home" +msgstr "Hauptseite" + +#: apps/base/templates/components.scm:150 +msgid "archive" +msgstr "Archiv" + +#: apps/base/templates/components.scm:289 +msgctxt "Website menu" +msgid "Guix" +msgstr "Guix" + +#: apps/base/templates/components.scm:293 +msgid "Website menu:" +msgstr "Men=C3=BC des Webauftritts:" + +#: apps/base/templates/components.scm:295 apps/base/templates/home.scm:3= 0 +msgctxt "Website menu" +msgid "Overview" +msgstr "=C3=9Cbersicht" + +#: apps/base/templates/components.scm:296 +msgctxt "Website menu" +msgid "Download" +msgstr "Herunterladen" + +#: apps/base/templates/components.scm:297 +msgctxt "Website menu" +msgid "Packages" +msgstr "Pakete" + +#: apps/base/templates/components.scm:298 +msgctxt "Website menu" +msgid "Blog" +msgstr "Blog" + +#: apps/base/templates/components.scm:299 +msgctxt "Website menu" +msgid "Help" +msgstr "Hilfe" + +#: apps/base/templates/components.scm:300 +msgctxt "Website menu" +msgid "Donate" +msgstr "Spenden" + +#: apps/base/templates/components.scm:305 apps/base/templates/contact.sc= m:31 +msgctxt "Website menu" +msgid "Contact" +msgstr "Kontakt" + +#: apps/base/templates/components.scm:306 +msgctxt "Website menu" +msgid "Contribute" +msgstr "Mitmachen" + +#: apps/base/templates/components.scm:307 +msgctxt "Website menu" +msgid "Security" +msgstr "Sicherheit" + +#: apps/base/templates/components.scm:308 +msgctxt "Website menu" +msgid "Graphics" +msgstr "Grafiken" + +#: apps/base/templates/components.scm:326 +msgid " (Page <1/> of <2/>)" +msgstr " (Seite <1/> von <2/>)" + +#: apps/base/templates/components.scm:350 +msgid "Page <1/> of <2/>. Go to another page: " +msgstr "Seite <1/> von <2/>. Besuchen Sie eine andere Seite: " + +#: apps/base/templates/contact.scm:17 apps/base/templates/contact.scm:36 +#: apps/base/templates/home.scm:232 +msgid "Contact" +msgstr "Kontakt" + +#: apps/base/templates/contact.scm:19 +msgid "" +"A list of channels to communicate with GNU=C2=A0Guix users\n" +" and developers about anything you want." +msgstr "" +"Eine Liste der Kan=C3=A4le, auf denen Sie mit Nutzern und Entwicklern v= on " +"GNU=C2=A0Guix reden k=C3=B6nnen, wor=C3=BCber Sie m=C3=B6chten." + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/contact.scm:23 +msgid "" +"GNU|Linux|Unix|Free software|Libre software|Operating system|GNU Hurd|G= NU " +"Guix package manager|Community|Mailing lists|IRC\n" +"channels|Bug reports|Help" +msgstr "" +"GNU|Linux|Unix|Freie Software|Libre-Software|Betriebssystem|GNU Hurd|GN= U-" +"Guix-Paketverwaltung|Gemeinde|Community|Mailing-Listen|IRC-Kan=C3=A4le|= Probleme " +"melden|Hilfe" + +#: apps/base/templates/home.scm:18 +msgid "GNU's advanced distro and transactional package manager" +msgstr "GNUs fortgeschrittene Distribution und transaktionelle Paketverw= altung" + +#. TRANSLATORS: |-separated list of webpage keywords +#: apps/base/templates/home.scm:26 +msgid "" +"GNU|Linux|Unix|Free software|Libre software|Operating system|GNU Hurd|G= NU " +"Guix package manager|GNU Guile|Guile Scheme|Transactional upgrades|" +"Functional package management|Reproducibility" +msgstr "" +"GNU|Linux|Unix|Freie Software|Libre-Software|Betriebssystem|GNU Hurd|GN= U-" +"Guix-Paketverwaltung|GNU Guile|Guile Scheme|Transaktionelle Aktualisier= ungen|" +"Funktionale Paketverwaltung|Reproduzierbarkeit" + +#: apps/base/templates/home.scm:39 +msgid "Summary" +msgstr "Zusammenfassung" + +#: apps/base/templates/home.scm:41 +msgid "" +"<1>Liberating. Guix is an advanced distribution of the <2>GNU opera= ting " +"system developed by the <3>GNU Project=E2=80=94which respects t= he <4>freedom " +"of computer users. " +msgstr "" +"<1>Befreiend. Guix ist eine fortgeschrittende Distribution des <2>G= NU-" +"Betriebssystems, das vom <3>GNU-Projekt entwickelt wurde und di= e " +"<4>Freiheit der Benutzer von Rechenger=C3=A4ten respektiert. " + +#: apps/base/templates/home.scm:59 +msgid "" +"<1>Dependable. Guix <2>supports transactional upgrades and roll= -" +"backs, unprivileged package management, <3>and more. When used as = a " +"standalone distribution, Guix supports <4>declarative system " +"configuration for transparent and reproducible operating systems." +msgstr "" +"<1>Verl=C3=A4sslich. Guix <2>unterst=C3=BCtzt transaktionelle A= ktualisierungen " +"und R=C3=BCcksetzungen, =E2=80=9Eunprivilegierte=E2=80=9C Paketverwaltu= ng f=C3=BCr Nutzer ohne " +"besondere Berechtigungen <3>und noch mehr. Wenn es als eigenst=C3=A4= ndige " +"Distribution verwendet wird, unterst=C3=BCtzt Guix eine <4>deklarative = " +"Konfiguration des Systems f=C3=BCr transparente und reproduzierbare= " +"Betriebssysteme." + +#: apps/base/templates/home.scm:77 +msgid "" +"<1>Hackable. It provides <2>Guile Scheme APIs, including high-l= evel " +"embedded domain-specific languages (EDSLs) to <3>define packages an= d " +"<4>whole-system configurations." +msgstr "" +"<1>Hackbar. Programmierschnittstellen (APIs) in <2>Guile Scheme= " +"werden zur Verf=C3=BCgung gestellt, einschlie=C3=9Flich hochsprachliche= r eingebetteter " +"dom=C3=A4nenspezifischer Sprachen (Embedded Domain-Specific Languages, = EDSLs), " +"mit denen Sie <3>Pakete definieren und <4>Konfigurationen des gesam= ten " +"Systems festlegen k=C3=B6nnen." + +#: apps/base/templates/home.scm:99 +msgctxt "button" +msgid "DOWNLOAD v<1/>" +msgstr "v<1/> HERUNTERLADEN" + +#: apps/base/templates/home.scm:104 +msgctxt "button" +msgid "CONTRIBUTE" +msgstr "MITMACHEN" + +#: apps/base/templates/home.scm:111 +msgid "Discover Guix" +msgstr "Entdecken Sie Guix" + +#: apps/base/templates/home.scm:113 +msgid "" +"Guix comes with thousands of packages which include applications, syste= m " +"tools, documentation, fonts, and other digital goods readily available = for " +"installing with the <1>GNU Guix package manager." +msgstr "" +"Mit Guix kommen Tausende von Paketen. Dazu geh=C3=B6ren Anwendungen, " +"Systemwerkzeuge, Dokumentation, Schriftarten, sowie andere digitale G=C3= =BCter, " +"die jederzeit zur Installation mit dem Paketverwaltungswerkzeug <1>GNU = " +"Guix bereitstehen." + +#: apps/base/templates/home.scm:130 +msgctxt "button" +msgid "ALL PACKAGES" +msgstr "ALLE PAKETE" + +#: apps/base/templates/home.scm:137 +msgid "GNU Guix in your field" +msgstr "GNU Guix in Ihrem Bereich" + +#: apps/base/templates/home.scm:139 +msgid "" +"Read some stories about how people are using GNU=C2=A0Guix in\n" +"their daily lives." +msgstr "" +"Lesen Sie ein paar Erfahrungen, wie die Leute GNU=C2=A0Guix in\n" +"ihrem t=C3=A4glichen Leben benutzen." + +#: apps/base/templates/home.scm:150 +msgctxt "button" +msgid "SOFTWARE DEVELOPMENT" +msgstr "SOFTWARE-ENTWICKLUNG" + +#: apps/base/templates/home.scm:155 +msgctxt "button" +msgid "BIOINFORMATICS" +msgstr "BIOINFORMATIK" + +#: apps/base/templates/home.scm:160 +msgctxt "button" +msgid "HIGH PERFORMANCE COMPUTING" +msgstr "HOCHLEISTUNGSRECHNEN" + +#: apps/base/templates/home.scm:165 +msgctxt "button" +msgid "RESEARCH" +msgstr "FORSCHUNG" + +#: apps/base/templates/home.scm:170 +msgctxt "button" +msgid "ALL FIELDS..." +msgstr "ALLE BEREICHE =E2=80=A6" + +#: apps/base/templates/home.scm:177 +msgid "GNU Guix in other GNU/Linux distros" +msgstr "GNU Guix auf anderen GNU/Linux-Distributionen" + +#: apps/base/templates/home.scm:189 +msgid "" +"Video: <1>Demo of Guix in another GNU/Linux distribution<1.1/>https://a= udio-" +"video.gnu.org/video/misc/2016-07__GNU_Guix_Demo_2.webm (1 minute, 3= 0 " +"seconds)." +msgstr "" +"Video: <1>Vorf=C3=BChrung von Guix auf einer anderen GNU/Linux-Distribu= tion<1.1/" +">https://audio-video.gnu.org/video/misc/2016-07__GNU_Guix_Demo_2.webm (1 " +"Minute, 30 Sekunden)." + +#: apps/base/templates/home.scm:200 +msgid "" +"If you don't use GNU=C2=A0Guix as a standalone GNU/Linux distribution, = you still " +"can use it as a package manager on top of any GNU/Linux distribution. T= his " +"way, you can benefit from all its conveniences." +msgstr "" +"Wenn Sie GNU=C2=A0Guix nicht als eine eigenst=C3=A4ndige GNU/Linux-Dist= ribution " +"verwenden, k=C3=B6nnen Sie es trotzdem zur Paketverwaltung benutzen, au= fgesetzt " +"auf eine beliebige bestehende GNU/Linux-Distribution. Auf diese Weise k= =C3=B6nnen " +"Sie all seine Vorteile genie=C3=9Fen." + +#: apps/base/templates/home.scm:205 +msgid "" +"Guix won't interfere with the package manager that comes with your " +"distribution. They can live together." +msgstr "" +"Guix und das Paketverwaltungswerkzeug, das mit Ihrer Distribution " +"ausgeliefert wird, werden sich gegenseitig nicht st=C3=B6ren. Sie k=C3=B6= nnen " +"friedlich koexistieren." + +#: apps/base/templates/home.scm:212 +msgctxt "button" +msgid "TRY IT OUT!" +msgstr "PROBIEREN SIE ES AUS!" + +#: apps/base/templates/home.scm:219 +msgid "Blog" +msgstr "Blog" + +#: apps/base/templates/home.scm:226 +msgctxt "button" +msgid "ALL POSTS" +msgstr "ALLE EINTR=C3=84GE" + +#: apps/base/templates/home.scm:239 +msgctxt "button" +msgid "ALL CONTACT MEDIA" +msgstr "ALLE KONTAKTM=C3=96GLICHKEITEN" + +#: apps/base/templates/theme.scm:73 apps/base/templates/theme.scm:75 +msgctxt "webpage title" +msgid "GNU=C2=A0Guix" +msgstr "GNU=C2=A0Guix" + +#: apps/base/templates/theme.scm:97 +msgctxt "webpage title" +msgid "GNU=C2=A0Guix =E2=80=94 Activity Feed" +msgstr "GNU=C2=A0Guix =E2=80=94 Aktivit=C3=A4ten-Feed" + +#: apps/base/templates/theme.scm:114 +msgid "" +"Made with <1>=E2=99=A5 by humans and powered by <2>GNU Guile. = <3>Source " +"code under the <4>GNU AGPL." +msgstr "" +"Mit <1>=E2=99=A5 von Menschen gemacht und durch <2>GNU Guile er= m=C3=B6glicht. " +"<3>Quellcode unter der <4>GNU AGPL." --=20 2.22.0 --cyuyic77bivek3sb--