Μετάβαση στο περιεχόμενο

Οδηγός

Αυτή η σελίδα είναι η αναφορά του χρόνου εκτέλεσης: όλα όσα κάνει ο κώδικας της εφαρμογής σας με αυτή τη βιβλιοθήκη αφού πια υπάρχουν κατάλογοι. Αν δεν έχετε δει ακόμη τον πλήρη βρόχο — επισήμανση, εξαγωγή, μετάφραση, μεταγλώττιση, εκτέλεση — η εκμάθηση τον διατρέχει μία φορά σε πέντε λεπτά· η δημιουργία και η επικύρωση καταλόγων καλύπτεται στην Εξαγωγή, και το πώς μια ομάδα κρατά τον βρόχο σε κίνηση — κύκλοι ενημέρωσης, CI, πλατφόρμες μετάφρασης — είναι το Στην παραγωγή.

Ποιο σημείο εισόδου να χρησιμοποιήσω;

Το πακέτο εξάγει αρκετούς τρόπους μετάφρασης ενός μηνύματος, επειδή οι εφαρμογές δένουν μια γλώσσα με αρκετούς διαφορετικούς τρόπους. Διαλέξτε με βάση το πώς αποφασίζει το πρόγραμμά σας σε ποια γλώσσα βρίσκεται:

Η περίπτωσή σας Χρησιμοποιήστε
Μία γλώσσα για ολόκληρη τη διεργασία — ένα CLI, μια εφαρμογή γραφείου, ένα σενάριο Translator, καλούμενος ως _
Μία γλώσσα ανά αίτημα ή ανά ασύγχρονη εργασία — μια εφαρμογή ιστού use_translations() γύρω από τη δουλειά, και μετά tr()
Ένα μήνυμα που ορίζεται κατά την εισαγωγή — ετικέτα φόρμας, enum, σταθερά lazy_gettext() ή lazy_pgettext()
Ένα πλήθος αποφασίζει τη διατύπωση ngettext() / npgettext(), σε όποια από τις παραπάνω μορφές
Απόδοση ενός μοτίβου χωρίς να εμπλέκεται κατάλογος compile_template()

Όλα όσα ακολουθούν είναι αυτά τα πέντε, με αυτή τη σειρά.

Δέσιμο ενός καταλόγου

Η προτεινόμενη μορφή αντικατοπτρίζει τη χρήση του gettext με κλάσεις: δέστε ένα τυπικό αντικείμενο μετάφρασης μία φορά και χρησιμοποιήστε τον καλέσιμο επεξεργαστή ως _.

import gettext

from gettext_tstrings import Translator

translations = gettext.translation("messages", localedir="locales", languages=["ja"])
_ = Translator(translations)

name = "Ada"
print(_(t"Hello {name}"))  # こんにちは Ada

n = 3
print(_.ngettext(t"One file", t"{n} files", n))  # picks the right plural form for n

filename = "report.txt"
print(_.pgettext("button", t"Open {filename}"))  # "button" disambiguates homonyms

Οι συναρτήσεις επιπέδου αρθρώματος ακολουθούν τα ονόματα της τυπικής βιβλιοθήκης και τη σύμβαση κλήσης της με ορίσματα μόνο κατά θέση:

from gettext_tstrings import gettext, ngettext, npgettext, pgettext

gettext(t"Hello {name}", translations=translations)
ngettext(t"One file", t"{n} files", n, translations=translations)
pgettext("button", t"Open {filename}", translations=translations)
npgettext("inbox", t"One message", t"{n} messages", n, translations=translations)

Οι tr και ntr είναι ακριβή ψευδώνυμα των gettext και ngettext.

Γλώσσα ανά αίτημα

Ένα πλαίσιο ιστού επιλέγει γλώσσα ανά αίτημα. Δέστε τις μεταφράσεις του αιτήματος στο τρέχον συγκείμενο και κάθε κλήση επιπέδου αρθρώματος επιλύεται σε αυτή τη γλώσσα, με ασφάλεια ανάμεσα σε ταυτόχρονα αιτήματα:

from gettext_tstrings import tr, use_translations


def handle(request):
    name = request.user.display_name
    translations = load_translations(request.locale)
    with use_translations(translations):
        return render(tr(t"Hello {name}"))

Η set_translations(translations) δένει χωρίς μπλοκ with, για πλαίσια που διαχειρίζονται μόνα τους τον κύκλο ζωής του αιτήματος· η get_translations() διαβάζει το τρέχον δέσιμο. Ένα ρητό όρισμα translations= υπερισχύει πάντα του συγκειμένου, και ένα αδέσμευτο συγκείμενο υποχωρεί στις καθολικά εγκατεστημένες συναρτήσεις gettext της τυπικής βιβλιοθήκης. Ολοκληρωμένα παραδείγματα για Flask και ενδιάμεσο λογισμικό ASGI υπάρχουν στη σελίδα Στην παραγωγή.

Αναβαλλόμενη μετάφραση

Ένα t-string αιχμαλωτίζει τις τιμές του άπληστα, πράγμα λάθος για μια συμβολοσειρά που ορίζεται κατά την εισαγωγή — μια ετικέτα φόρμας, μια τιμή enum, μια σταθερά αρθρώματος — και πρέπει να αποδοθεί σε όποια γλώσσα είναι ενεργή όταν χρησιμοποιείται.

from gettext_tstrings import lazy_gettext, lazy_pgettext, use_translations

SAVE = lazy_gettext(t"Save changes")  # defined once, at import
OPEN = lazy_pgettext("button", t"Open file")

with use_translations(japanese):
    assert str(SAVE) == "変更を保存"  # rendered here, in this language

Μια LazyString αποδίδεται μέσω str(), format() και f-strings, και συγκρίνεται ίση με το αποδιδόμενο κείμενό της.

Σκόπιμα μη κατακερματίσιμη

Το κείμενο μιας LazyString εξαρτάται από την ενεργή γλώσσα, οπότε ένα hash θα άλλαζε με κάθε εναλλαγή γλώσσας και θα αλλοίωνε αθόρυβα κάθε set ή dict που την κρατά. Καλέστε πρώτα str() αν χρειάζεστε κλειδί.

Το strict αποφασίζεται εκεί όπου γράφεται το μήνυμα, όχι εκεί όπου αποδίδεται:

SAVE = lazy_gettext(t"Save changes", strict=True)

Μια αναβαλλόμενη συμβολοσειρά αποδίδεται όπου τελικά χρησιμοποιείται — μέσα σε ένα πρότυπο, μια φόρμα, μια γραμμή καταγραφής — και αυτό το σημείο σπάνια γνωρίζει αν πρόκειται για εκτέλεση δοκιμών ή για παραγωγή. Το πέρασμα strict=True κατά τον ορισμό είναι που επιτρέπει να ισχύσει η ίδια επιλογή θορυβώδης στο CI, επιεικής στην παραγωγή και για μια συμβολοσειρά που δεν αποδίδεται στο σημείο κλήσης της.

Οι μορφές πληθυντικού εξαρτώνται από ένα πλήθος γνωστό στον χρόνο εκτέλεσης, οπότε αποδώστε τες άπληστα με την ngettext εκεί όπου το πλήθος είναι γνωστό.

Πολλές γλώσσες ταυτόχρονα

Ένα αίτημα χρειάζεται συχνά περισσότερες από μία γλώσσες: μια σελίδα που αποδίδεται για τον αναγνώστη και συγχρόνως βάζει σε ουρά μια ειδοποίηση για έναν λογαριασμό ρυθμισμένο σε άλλη, ή μια σύνοψη που παραθέτει κάθε συμμετέχοντα στη δική του. Τα δεσίματα φωλιάζουν, και η έξοδος από το εσωτερικό μπλοκ επαναφέρει το εξωτερικό.

with use_translations(reader):
    page = tr(t"Hello {name}")
    with use_translations(recipient):
        notice = tr(t"Hello {name}")  # the recipient's language
    footer = tr(t"Hello {name}")  # the reader's again

Πάνω σε μια λίστα παραληπτών, τη δουλειά την κάνουν οι αναβαλλόμενες συμβολοσειρές: το μήνυμα γράφεται μία φορά, κατά την εισαγωγή, και αποδίδεται μία φορά ανά γλώσσα.

SUBJECT = lazy_gettext(t"Your order shipped")

for user in users:
    with use_translations(load_translations(user.locale)):
        send(user.email, str(SUBJECT))

Το δέσιμο είναι ένα ContextVar, όχι μια στοίβα που κρατιέται πάνω σε ένα κοινό αντικείμενο, οπότε αιτήματα που επικαλύπτονται δεν μπορούν να πάρουν το ένα τη γλώσσα του άλλου — ούτε καν στην περίπτωση όπου βγαίνουν από τα μπλοκ τους με τη σειρά που μπήκαν, που είναι ακριβώς η αλληλοδιαπλοκή στην οποία σφάλλει μια στοίβα. Η φόρτωση ενός καταλόγου ανά γλώσσα είναι φθηνή: η gettext.translation() αναλύει κάθε .mo μία φορά και μοιράζει αντίγραφα που μοιράζονται τον αναλυμένο κατάλογο.

Το αν ένα νήμα εργασίας κληρονομεί το δέσιμο εξαρτάται από το build

Ένα γυμνό threading.Thread, ή η ThreadPoolExecutor.submit, ξεκινά είτε από ένα αντίγραφο του συγκειμένου του καλούντος είτε από ένα άδειο, και το ποιο από τα δύο θα συμβεί το κρίνει η sys.flags.thread_inherit_context — αληθής εξ ορισμού στα free-threaded builds, ψευδής παντού αλλού. Ο ίδιος κώδικας αποδίδει επομένως τη δεσμευμένη γλώσσα στην 3.14t και τον καθολικό για τη διεργασία κατάλογο στην 3.14. Περάστε το συγκείμενο αντί να στηρίζεστε στην προεπιλογή:

pool.submit(contextvars.copy_context().run, render)

Η asyncio.to_thread το κάνει ήδη αυτό για εσάς.

Τιμές με επίγνωση τοπικών ρυθμίσεων

Αυτή η βιβλιοθήκη αποφασίζει πού εμφανίζεται μια τιμή μέσα σε ένα μεταφρασμένο μήνυμα. Δεν τοπικοποιεί την ίδια την τιμή. Το {amount:,.2f} είναι μια προδιαγραφή μορφοποίησης της Python με σταθερή συμπεριφορά — ένα κόμμα κάθε τρία ψηφία και μια τελεία πριν από τα δεκαδικά — και παράγει τους ίδιους χαρακτήρες σε όποια γλώσσα κι αν είναι το μήνυμα:

>>> f"{1234.5:,.2f}"  # the same in every locale
'1,234.50'

Τα γερμανικά γράφουν αυτόν τον αριθμό 1.234,50, τα γαλλικά 1 234,50, και τα χίντι ομαδοποιούν το 1234567 ως 12,34,567 και όχι ως 1,234,567. Οι αριθμοί, τα νομίσματα, οι ημερομηνίες, οι ώρες και οι μονάδες ανήκουν στο Babel. Μορφοποιήστε πρώτα την τιμή και μετά τοποθετήστε την έτοιμη συμβολοσειρά:

from babel.numbers import format_currency

total = format_currency(amount, "EUR", locale=locale)
tr(t"Your order comes to {total}")

Σε ένα μήνυμα με πλήθος, ο αριθμός κάνει δύο δουλειές — επιλέγει τη μορφή πληθυντικού και εμφανίζεται μέσα στο κείμενο — και μόνο η δεύτερη τοπικοποιείται. Κρατήστε το ακατέργαστο πλήθος για την επιλογή και περάστε τη μορφοποιημένη συμβολοσειρά για την εμφάνιση:

from babel.numbers import format_decimal

shown = format_decimal(n, locale=locale)
_.ngettext(t"One file", t"{shown} files", n)

Η μορφοποίηση πριν από την κλήση είναι επίσης αυτό που κρατά μια προδιαγραφή μορφοποίησης έξω από τον κατάλογο: αυτό που βλέπει ένας μεταφραστής είναι ένα τελειωμένο κομμάτι κειμένου, όχι ένας αριθμός συν οδηγίες για την απόδοσή του.

Τι συμβαίνει όταν ένας κατάλογος είναι λανθασμένος

Αν τα σύμβολα κράτησης θέσης μιας μετάφρασης δεν ταιριάζουν με την πηγή — ένα πεδίο που λείπει, είναι άγνωστο ή έχει αλλαγμένη μορφοποίηση και ξέφυγε από την επικύρωση, από ένα χειροποίητο MO, έναν κατάλογο προμηθευτή ή μια γραμμή παραγωγής που παραλείπει τον ελεγκτή — η προεπιλογή είναι να αποδοθεί το πηγαίο μήνυμα αντί να εγερθεί εξαίρεση. Αυτό αντικατοπτρίζει το ίδιο το συμβόλαιο του gettext ότι ένας κακός κατάλογος δεν σπάει ποτέ την εφαρμογή.

Με το Hello {name} μεταφρασμένο ως こんにちは {nombre}, η απόδοση επιτυγχάνει και μία προειδοποίηση πηγαίνει στον καταγραφέα gettext_tstrings:

WARNING gettext_tstrings: invalid translation for msgid 'Hello {name}'; using
source text: translation does not match the source placeholders: {name} is
missing; {nombre} is not in the source message
>>> _(t"Hello {name}")
'Hello Ada'

Η προειδοποίηση εκπέμπεται μία φορά ανά μήνυμα και μοτίβο, όχι μία φορά ανά απόδοση, ώστε μια χαλασμένη καταχώριση καταλόγου να μην πλημμυρίζει ένα αρχείο καταγραφής.

Επιλέξτε τη θορυβώδη αποτυχία για δοκιμές και CI:

strict = Translator(translations, strict=True)
tr(t"Hello {name}", translations=translations, strict=True)

Η ίδια αναζήτηση τότε εγείρει εξαίρεση, κουβαλώντας την ίδια πρόταση χωρίς το μισό «using source text»:

>>> strict(t"Hello {name}")
Traceback (most recent call last):
  ...
gettext_tstrings.errors.InvalidTranslationError: translation does not match the
source placeholders: {name} is missing; {nombre} is not in the source message

Αυτά τα μηνύματα γράφονται για όποιον μπορεί να ενεργήσει πάνω τους, που για ένα πρόβλημα καταλόγου είναι συχνότερα μεταφραστής παρά προγραμματιστής — οπότε όπου ένα σύμβολο κράτησης θέσης μοιάζει παρόν αλλά δεν είναι, το μήνυμα εξηγεί γιατί αντί να επαναλάβει ότι λείπει. Άγκιστρα πλήρους πλάτους, ένα διπλασιασμένο {{name}}, ένα αόρατο διάστημα χωρίς αλλαγή γραμμής, ένα κυριλλικό γράμμα ανάμεσα σε λατινικά: καθένα έχει τη δική του διατύπωση, και όλα παρατίθενται με παραδείγματα στο Για μεταφραστές. Εκείνη η σελίδα είναι γραμμένη για να δοθεί σε όποιον επεξεργάζεται το .po.

Απόδοση ενός μοτίβου χωρίς κατάλογο

Η compile_template εκθέτει τον ίδιο μηχανισμό ένα επίπεδο πιο κάτω: μετατρέπει ένα t-string στο msgid του συν ένα δεμένο σύνολο τιμών, και αποδίδει όποιο μοτίβο της δώσετε.

from gettext_tstrings import compile_template

name = "Ada"
compiled = compile_template(t"Hello {name}")

compiled.msgid  # "Hello {name}"
compiled.placeholders  # ("name",)
compiled.render("こんにちは {name}")  # "こんにちは Ada"

Η render επικυρώνει με τους ίδιους κανόνες και εγείρει πάντα εξαίρεση σε αναντιστοιχία. Εδώ δεν υπάρχει επιεικής λειτουργία: η επιείκεια υπάρχει για να μπορεί μια αναζήτηση καταλόγου να υποβαθμιστεί στο πηγαίο κείμενο, και ένα μοτίβο που περάσατε εσείς οι ίδιοι δεν έχει από πού να υποβαθμιστεί.

Ασφάλεια και εμβέλεια

Αυτό είναι έγκυρο:

tr(t"Hello {name}")

Αυτά απορρίπτονται σκόπιμα:

tr(t"Hello {user.name}")  # attribute access
tr(t"Hello {display_name()}")  # a call

Υπολογίστε πρώτα μια ουσιαστική τιμή:

name = user.display_name()
tr(t"Hello {name}")

Ο περιορισμός παράγει σταθερά κλειδιά καταλόγου, δίνει στους μεταφραστές χρήσιμα ονόματα, και εμποδίζει μια μεταφρασμένη συμβολοσειρά να γίνει γλώσσα εκφράσεων.

Η εγγύηση περιορίζεται σε δομή και μορφοποίηση: μια μετάφραση δεν αποτιμάται ποτέ, και δεν μπορεί ποτέ να προσθέσει πρόσβαση σε ιδιότητες, κλήσεις, μετατροπές ή προδιαγραφές μορφοποίησης. Δύο πράγματα παραμένουν ευθύνη του καλούντος, ακριβώς όπως και με το gettext της τυπικής βιβλιοθήκης — η διαφυγή της αποδιδόμενης εξόδου για τον προορισμό της (HTML, φλοιός, τερματικό), και η ακεραιότητα του καταλόγου, αφού ένας εχθρικός κατάλογος μπορεί να επαναλάβει ένα σύμβολο κράτησης θέσης για να πολλαπλασιάσει το μέγεθος της εξόδου, κάτι εγγενές σε κάθε i18n βασισμένο σε σύμβολα κράτησης θέσης.