"""Bygger e-postmalene til hvelvet, ett sett per språk. Vaultwarden har 30 maler. Vi hadde skrevet sju av dem om til norsk; resten gikk ut på engelsk med Bitwardens navn i teksten - altså midt i onboardingen. Å håndskrive de 23 siste i to formater og to språk blir 92 filer som må holdes i takt, så teksten ligger heller i tabellen under og formene genereres. Kjøres under bygget: python3 maler.py og legger igjen //email/.hbs og .html.hbs. Et nytt språk er en kolonne til i tabellen, ikke en ny mappe med filer. """ import re import sys import textwrap from pathlib import Path SPRÅK = ("nb", "en") # Bunnteksten under strekend i hver e-post. BUNN = { "nb": "Vaultnord · drift i Finland, backup i Tyskland — alt innenfor EU.", "en": "Vaultnord · hosted in Finland, backed up in Germany — all within the EU.", } BUNN_TEKST = {"nb": "Vaultnord", "en": "Vaultnord"} # Navnet på appen kunden faktisk installerer. Den heter Bitwarden i butikkene, # og å kalle den noe annet ville sende folk på et umulig søk. APP = "Bitwarden" # --- oppmerking ------------------------------------------------------------ # Teksten skrives én gang. [tekst](lenke) blir en lenke, *ord* blir uthevet, og # linjer som begynner med "- " blir en liste. Alt annet er avsnitt. LENKE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") UTHEVET = re.compile(r"\*([^*]+)\*") BARE_LENKE = re.compile(r"^\[([^\]]+)\]\(([^)]+)\)$") def _tekst(avsnitt: list[str]) -> str: ut = [] for a in avsnitt: if a.startswith("- "): ut.append("\n".join(" " + linje[2:] for linje in a.split("\n"))) continue # En lenke som står alene er en oppfordring: sett adressen på egen linje, # uinnpakket, så den blir klikkbar i alle klienter. alene = BARE_LENKE.match(a) if alene: ut.append(f"{alene.group(1)}:\n{alene.group(2)}") continue ut.append(textwrap.fill(LENKE.sub(_flat, a), 78, break_long_words=False, break_on_hyphens=False)) return "\n\n".join(ut) def _flat(m: re.Match) -> str: """Lenke midt i en setning. Er teksten adressen selv, skal den ikke stå to ganger - da ble det «{{url}}: {{{url}}}» i innboksen.""" tekst, adresse = m.group(1), m.group(2) if tekst.strip("{} ") == adresse.strip("{} "): return tekst return f"{tekst} ({adresse})" def _html(avsnitt: list[str]) -> str: p = ('margin:0 0 16px;color:#2e3440;font-size:15px;line-height:1.6') ut = [] for a in avsnitt: if a.startswith("- "): punkter = "".join( f'
  • {_inline(linje[2:])}
  • ' for linje in a.split("\n")) ut.append(f'
      {punkter}
    ') else: ut.append(f'

    {_inline(a)}

    ') return "\n ".join(ut) def _inline(s: str) -> str: s = LENKE.sub(r'\1', s) return UTHEVET.sub(r"\1", s) RAMME = """
    vaultnord

    {tittel}

    {kropp}
    {bunn}
    """ HTML_LANG = {"nb": "no", "en": "en"} # --- teksten --------------------------------------------------------------- # Per mal og språk: (emne, overskrift, avsnitt). Variabelnavnene er # Vaultwardens egne - endres de her, kommer e-posten ut med tomme felter. MALER: dict[str, dict[str, tuple[str, str, list[str]]]] = { "welcome": { "nb": ("Hvelvet ditt er klart til bruk", "Hvelvet ditt er klart", [ "Hovedpassordet ditt er satt, og hvelvet er klart til bruk på [{{url}}]({{{url}}}).", f"Ett tips til slutt: installer {APP}-appen eller nettleserutvidelsen og oppgi " "adressen over som serveradresse. Nettleserversjonen låser seg hver gang siden " "lastes på nytt, fordi nøkkelen bare finnes i minnet — utvidelsen holder økten.", ]), "en": ("Your vault is ready", "Your vault is ready", [ "Your master password is set, and your vault is ready at [{{url}}]({{{url}}}).", f"One tip: install the {APP} app or browser extension and enter the address " "above as the server address. The web version locks every time the page " "reloads, because the key only lives in memory — the extension keeps the session.", ]), }, "welcome_must_verify": { "nb": ("Bekreft e-postadressen din", "Ett steg igjen", [ "Kontoen din på [{{url}}]({{{url}}}) er opprettet. Før du kan logge inn må du " "bekrefte at adressen er din.", "[Bekreft e-postadressen]({{url}}/#/verify-email/?userId={{user_id}}&token={{token}})", "Var det ikke du som opprettet kontoen, kan du se bort fra e-posten.", ]), "en": ("Verify your email address", "One step left", [ "Your account at [{{url}}]({{{url}}}) has been created. Before you can log in, " "you need to confirm that this address is yours.", "[Verify your email address]({{url}}/#/verify-email/?userId={{user_id}}&token={{token}})", "If you did not create this account, you can ignore this email.", ]), }, "verify_email": { "nb": ("Bekreft e-postadressen din", "Bekreft e-postadressen", [ "Bekreft e-postadressen for kontoen din ved å åpne lenken under:", "[Bekreft e-postadressen]({{url}}/#/verify-email/?userId={{user_id}}&token={{token}})", "Var det ikke du som ba om dette, kan du se bort fra e-posten.", ]), "en": ("Verify your email address", "Verify your email address", [ "Confirm the email address on your account by opening the link below:", "[Verify your email address]({{url}}/#/verify-email/?userId={{user_id}}&token={{token}})", "If you did not ask for this, you can ignore this email.", ]), }, "register_verify_email": { "nb": ("Bekreft e-postadressen din", "Bekreft e-postadressen", [ "Bekreft adressen for å fullføre opprettelsen av kontoen.", "[Bekreft e-postadressen]({{{url}}})", "Var det ikke du som ba om dette, kan du se bort fra e-posten.", ]), "en": ("Verify your email address", "Verify your email address", [ "Confirm this address to finish creating your account.", "[Verify your email address]({{{url}}})", "If you did not ask for this, you can ignore this email.", ]), }, "send_org_invite": { "nb": ("Invitasjon til passordhvelvet hos {{{org_name}}}", "Du er invitert", [ "Du er invitert til passordhvelvet til {{org_name}}.", "Åpne lenken under for å komme i gang. Der setter du ditt eget hovedpassord — " "det er bare du som kjenner det, og det kan ikke gjenopprettes av noen andre.", "[Bli med]({{{url}}})", "Lenken er gyldig i fem dager. Bruk hele adressen, også den lange tegnrekken til slutt.", "Var ikke dette ventet? Da kan du se bort fra e-posten.", ]), "en": ("Invitation to the password vault at {{{org_name}}}", "You have been invited", [ "You have been invited to the password vault of {{org_name}}.", "Open the link below to get started. There you set your own master password — " "you are the only one who knows it, and nobody else can recover it.", "[Join]({{{url}}})", "The link is valid for five days. Use the whole address, including the long string at the end.", "Were you not expecting this? Then you can ignore this email.", ]), }, "invite_accepted": { "nb": ("{{{email}}} har takket ja til invitasjonen", "Én bekreftelse gjenstår", [ "{{email}} har satt hovedpassordet sitt og takket ja til invitasjonen til {{org_name}}.", "Siste steg må gjøres av deg: logg inn på [{{url}}]({{{url}}}) og bekreft brukeren " "under Medlemmer i organisasjonen. Først da får de tilgang til det som deles.", "Bekreftelsen kan bare gjøres av deg — den krever organisasjonsnøkkelen, som bare " "finnes i din egen innloggede klient. Det er derfor vi ikke kan gjøre det for deg.", ]), "en": ("{{{email}}} accepted the invitation", "One confirmation left", [ "{{email}} has set their master password and accepted the invitation to {{org_name}}.", "The last step is yours: log in at [{{url}}]({{{url}}}) and confirm the user under " "Members in the organisation. Only then do they get access to what is shared.", "Only you can confirm — it requires the organisation key, which exists solely in " "your own logged-in client. That is why we cannot do it for you.", ]), }, "invite_confirmed": { "nb": ("Du har fått tilgang til {{{org_name}}}", "Du er med", [ "Du er nå bekreftet som medlem av {{org_name}}.", "Passordene organisasjonen deler med deg dukker opp i hvelvet ditt: [{{url}}]({{{url}}}).", f"Bruker du {APP}-appen eller nettleserutvidelsen, kan du måtte synkronisere en gang for å se dem.", ]), "en": ("You now have access to {{{org_name}}}", "You are in", [ "You have been confirmed as a member of {{org_name}}.", "The passwords the organisation shares with you appear in your vault: [{{url}}]({{{url}}}).", f"If you use the {APP} app or browser extension, you may need to sync once to see them.", ]), }, "new_device_logged_in": { "nb": ("Ny innlogging fra {{{device_name}}}", "Ny innlogging", [ "Kontoen din ble nettopp brukt til å logge inn fra en ny enhet.", "- Tidspunkt: {{datetime}}\n- IP-adresse: {{ip}}\n- Enhet: {{device_name}} ({{device_type}})", "Var det deg, trenger du ikke gjøre noe.", "Kjenner du deg ikke igjen: bytt hovedpassord med en gang, og logg ut alle enheter " "under Innstillinger i hvelvet ([{{url}}]({{{url}}})).", ]), "en": ("New login from {{{device_name}}}", "New login", [ "Your account was just used to log in from a new device.", "- Time: {{datetime}}\n- IP address: {{ip}}\n- Device: {{device_name}} ({{device_type}})", "If this was you, there is nothing to do.", "If you do not recognise it: change your master password right away, and sign out " "all devices under Settings in the vault ([{{url}}]({{{url}}})).", ]), }, "incomplete_2fa_login": { "nb": ("Avbrutt innlogging fra {{{device_name}}}", "Noen kom halvveis inn", [ "Noen logget inn på kontoen din med riktig hovedpassord, men fullførte ikke " "totrinnsbekreftelsen innen {{time_limit}} minutter.", "- Tidspunkt: {{datetime}}\n- IP-adresse: {{ip}}\n- Enhet: {{device_name}} ({{device_type}})", "Var det ikke du eller noen du kjenner: *bytt hovedpassord med en gang*. At passordet " "var riktig betyr at det er på avveie.", ]), "en": ("Interrupted login from {{{device_name}}}", "Someone got halfway in", [ "Someone logged into your account with the correct master password, but did not " "complete two-step verification within {{time_limit}} minutes.", "- Time: {{datetime}}\n- IP address: {{ip}}\n- Device: {{device_name}} ({{device_type}})", "If this was not you or someone you know: *change your master password immediately*. " "The password being correct means it has leaked.", ]), }, "twofactor_email": { "nb": ("Innloggingskode: {{{token}}}", "Innloggingskode", [ "Koden din er: *{{token}}*", "Bruk den for å fullføre innloggingen. Den varer i noen få minutter.", "Var det ikke du som logget inn, bør du bytte hovedpassord.", ]), "en": ("Login code: {{{token}}}", "Login code", [ "Your code is: *{{token}}*", "Use it to finish logging in. It lasts a few minutes.", "If you were not the one logging in, you should change your master password.", ]), }, "protected_action": { "nb": ("Bekreftelseskode: {{{token}}}", "Bekreftelseskode", [ "Koden din er: *{{token}}*", "Bruk den for å fullføre handlingen du startet i hvelvet.", ]), "en": ("Verification code: {{{token}}}", "Verification code", [ "Your code is: *{{token}}*", "Use it to complete the action you started in the vault.", ]), }, "change_email": { "nb": ("Bytte av e-postadresse", "Bekreft den nye adressen", [ "Skriv denne koden i hvelvet for å fullføre byttet av e-postadresse: *{{token}}*", "Var det ikke du som ba om byttet, ta kontakt med administratoren din.", ]), "en": ("Email address change", "Confirm the new address", [ "Enter this code in the vault to finish changing your email address: *{{token}}*", "If you did not ask for the change, contact your administrator.", ]), }, "change_email_existing": { "nb": ("Noen forsøkte å bruke adressen din", "Adressen er allerede i bruk", [ "En bruker ({{ acting_address }}) forsøkte nettopp å flytte kontoen sin til denne " "adressen ({{ existing_address }}). Det finnes allerede en konto her.", "Var det ikke du som forsøkte, ta kontakt med administratoren din.", ]), "en": ("Someone tried to use your address", "The address is already in use", [ "A user ({{ acting_address }}) just tried to move their account to this address " "({{ existing_address }}). An account already exists here.", "If it was not you, contact your administrator.", ]), }, "change_email_invited": { "nb": ("Noen forsøkte å bruke adressen din", "Adressen er allerede invitert", [ "En bruker ({{ acting_address }}) forsøkte nettopp å flytte kontoen sin til denne " "adressen ({{ existing_address }}). Adressen er allerede invitert til et hvelv.", "Skal adressen brukes til noe annet, må kontoen som hører til den slettes først: " "[be om sletting]({{url}}/#/recover-delete). Invitasjonen må da sendes på nytt.", "Var det ikke du som forsøkte, ta kontakt med administratoren din.", ]), "en": ("Someone tried to use your address", "The address is already invited", [ "A user ({{ acting_address }}) just tried to move their account to this address " "({{ existing_address }}). The address has already been invited to a vault.", "To use the address for something else, the account tied to it must be deleted " "first: [request deletion]({{url}}/#/recover-delete). The invitation then has to be resent.", "If it was not you, contact your administrator.", ]), }, "delete_account": { "nb": ("Slett kontoen din", "Bekreft sletting", [ "Åpne lenken under for å slette kontoen din. *Alt innholdet i hvelvet forsvinner, " "og det kan ikke hentes tilbake.*", "[Slett kontoen]({{url}}/#/verify-recover-delete?userId={{user_id}}&token={{token}}&email={{email}})", "Var det ikke du som ba om dette, kan du se bort fra e-posten.", ]), "en": ("Delete your account", "Confirm deletion", [ "Open the link below to delete your account. *Everything in the vault is lost, " "and it cannot be brought back.*", "[Delete the account]({{url}}/#/verify-recover-delete?userId={{user_id}}&token={{token}}&email={{email}})", "If you did not ask for this, you can ignore this email.", ]), }, "pw_hint_some": { "nb": ("Hintet til hovedpassordet ditt", "Hintet ditt", [ "Noen ba om hintet til hovedpassordet ditt.", "Hintet er: *{{hint}}*", "[Logg inn i hvelvet]({{{url}}})", "Husker du ikke hovedpassordet, finnes det ingen vei tilbake til innholdet — det er " "kryptert med nøkkelen bare du har. Eneste utvei er å [slette kontoen]({{url}}/#/recover-delete) " "og begynne på nytt. Alt innholdet forsvinner da.", "Var det ikke du som ba om hintet, kan du se bort fra e-posten.", ]), "en": ("Your master password hint", "Your hint", [ "Someone asked for your master password hint.", "The hint is: *{{hint}}*", "[Log in to the vault]({{{url}}})", "If you cannot remember your master password, there is no way back to the contents — " "they are encrypted with a key only you hold. The only way out is to " "[delete the account]({{url}}/#/recover-delete) and start over. Everything in it is lost.", "If you did not ask for the hint, you can ignore this email.", ]), }, "pw_hint_none": { "nb": ("Hintet til hovedpassordet ditt", "Du har ikke lagret noe hint", [ "Noen ba om hintet til hovedpassordet ditt, men kontoen din har ikke noe hint lagret.", "Husker du ikke hovedpassordet, finnes det ingen vei tilbake til innholdet — det er " "kryptert med nøkkelen bare du har. Eneste utvei er å [slette kontoen]({{url}}/#/recover-delete) " "og begynne på nytt. Alt innholdet forsvinner da.", "Var det ikke du som ba om hintet, kan du se bort fra e-posten.", ]), "en": ("Your master password hint", "You have no hint saved", [ "Someone asked for your master password hint, but your account has none saved.", "If you cannot remember your master password, there is no way back to the contents — " "they are encrypted with a key only you hold. The only way out is to " "[delete the account]({{url}}/#/recover-delete) and start over. Everything in it is lost.", "If you did not ask for the hint, you can ignore this email.", ]), }, "admin_reset_password": { "nb": ("Hovedpassordet ditt er endret", "Hovedpassordet er endret", [ "Hovedpassordet til {{user_name}} er endret av en administrator i {{org_name}}.", "Var det ikke avtalt, ta kontakt med administratoren din med en gang.", ]), "en": ("Your master password has been changed", "Master password changed", [ "The master password for {{user_name}} was changed by an administrator in {{org_name}}.", "If this was not agreed, contact your administrator immediately.", ]), }, "send_2fa_removed_from_org": { "nb": ("Tilgangen din til {{{org_name}}} er fjernet", "Tilgangen er fjernet", [ "Du er tatt ut av {{org_name}} fordi kontoen din ikke har totrinnsbekreftelse.", "Slå på totrinnsbekreftelse under Innstillinger i hvelvet, så kan du bli med igjen.", ]), "en": ("Your access to {{{org_name}}} has been removed", "Access removed", [ "You were removed from {{org_name}} because your account has no two-step verification.", "Turn on two-step verification under Settings in the vault, and you can rejoin.", ]), }, "send_single_org_removed_from_org": { "nb": ("Tilgangen din til {{{org_name}}} er fjernet", "Tilgangen er fjernet", [ "Du er tatt ut av {{org_name}} fordi du også er medlem av en annen organisasjon. " "{{org_name}} tillater ikke det.", "Meld deg ut av de andre organisasjonene, eller bli med med en annen konto.", ]), "en": ("Your access to {{{org_name}}} has been removed", "Access removed", [ "You were removed from {{org_name}} because you are also a member of another " "organisation. {{org_name}} does not allow that.", "Leave the other organisations, or join with a different account.", ]), }, "sso_change_email": { "nb": ("E-postadressen din er endret", "Adressen er endret hos påloggingstjenesten", [ "E-postadressen din ble endret hos påloggingstjenesten. Oppdater den også i hvelvet, " "under Kontoinnstillinger: [{{url}}]({{{url}}}).", ]), "en": ("Your email address has changed", "The address changed at your login provider", [ "Your email address was changed at your login provider. Update it in the vault too, " "under Account settings: [{{url}}]({{{url}}}).", ]), }, "smtp_test": { "nb": ("Test av e-postoppsettet", "Testen kom fram", [ "Dette er en testmelding for e-postoppsettet til {{url}}.", "Kan du lese den, virker oppsettet.", ]), "en": ("Email configuration test", "The test arrived", [ "This is a test message for the email setup of {{url}}.", "If you can read it, the setup works.", ]), }, "send_emergency_access_invite": { "nb": ("Nødtilgang for {{{grantor_name}}}", "Du er spurt om å være nødkontakt", [ "{{grantor_name}} har spurt om du vil være nødkontakt for hvelvet sitt.", "[Ta imot]({{{url}}})", "Vil du ikke, kan du se bort fra e-posten.", ]), "en": ("Emergency access for {{{grantor_name}}}", "You have been asked to be an emergency contact", [ "{{grantor_name}} has asked you to be an emergency contact for their vault.", "[Accept]({{{url}}})", "If you would rather not, you can ignore this email.", ]), }, "emergency_access_invite_accepted": { "nb": ("{{{grantee_email}}} har takket ja", "Én bekreftelse gjenstår", [ "{{grantee_email}} har takket ja til å være nødkontakten din.", "Logg inn i hvelvet ([{{url}}]({{{url}}})) og bekreft dem under Innstillinger. " "Vil du likevel ikke, kan du fjerne dem samme sted.", ]), "en": ("{{{grantee_email}}} accepted", "One confirmation left", [ "{{grantee_email}} has accepted being your emergency contact.", "Log in to the vault ([{{url}}]({{{url}}})) and confirm them under Settings. " "If you have changed your mind, you can remove them in the same place.", ]), }, "emergency_access_invite_confirmed": { "nb": ("Du er nødkontakt for {{{grantor_name}}}", "Du er bekreftet", [ "Du er bekreftet som nødkontakt for *{{grantor_name}}*.", "Du kan be om nødtilgang fra hvelvet: [{{url}}]({{{url}}}).", ]), "en": ("You are an emergency contact for {{{grantor_name}}}", "You are confirmed", [ "You have been confirmed as an emergency contact for *{{grantor_name}}*.", "You can request emergency access from the vault: [{{url}}]({{{url}}}).", ]), }, "emergency_access_recovery_initiated": { "nb": ("{{{grantee_name}}} har bedt om nødtilgang", "Noen ber om nødtilgang", [ "{{grantee_name}} har bedt om nødtilgang for å {{atype}} kontoen din.", "Logg inn i hvelvet ([{{url}}]({{{url}}})) for å godta eller avslå.", "Gjør du ingenting, blir forespørselen *godtatt automatisk om {{wait_time_days}} dager*.", ]), "en": ("{{{grantee_name}}} requested emergency access", "Someone is requesting emergency access", [ "{{grantee_name}} has requested emergency access to {{atype}} your account.", "Log in to the vault ([{{url}}]({{{url}}})) to approve or reject it.", "If you do nothing, the request is *approved automatically in {{wait_time_days}} days*.", ]), }, "emergency_access_recovery_reminder": { "nb": ("{{{grantee_name}}} venter fortsatt på nødtilgang", "Forespørselen ligger fortsatt der", [ "{{grantee_name}} har en ubesvart forespørsel om nødtilgang for å {{atype}} kontoen din.", "Logg inn i hvelvet ([{{url}}]({{{url}}})) for å godta eller avslå.", "Gjør du ingenting, blir forespørselen *godtatt automatisk om {{days_left}} dager*.", ]), "en": ("{{{grantee_name}}} is still waiting for emergency access", "The request is still open", [ "{{grantee_name}} has a pending emergency access request to {{atype}} your account.", "Log in to the vault ([{{url}}]({{{url}}})) to approve or reject it.", "If you do nothing, the request is *approved automatically in {{days_left}} days*.", ]), }, "emergency_access_recovery_approved": { "nb": ("Nødtilgangen din er godtatt", "Godtatt", [ "{{grantor_name}} har godtatt forespørselen din om nødtilgang.", "Du kan nå logge inn i hvelvet: [{{url}}]({{{url}}}).", ]), "en": ("Your emergency access was approved", "Approved", [ "{{grantor_name}} approved your emergency access request.", "You can now log in to the vault: [{{url}}]({{{url}}}).", ]), }, "emergency_access_recovery_rejected": { "nb": ("Nødtilgangen din er avslått", "Avslått", [ "{{grantor_name}} har avslått forespørselen din om nødtilgang.", ]), "en": ("Your emergency access was rejected", "Rejected", [ "{{grantor_name}} rejected your emergency access request.", ]), }, "emergency_access_recovery_timed_out": { "nb": ("{{{grantee_name}}} har fått nødtilgang", "Fristen gikk ut", [ "{{grantee_name}} har fått nødtilgang til å {{atype}} kontoen din, fordi forespørselen " "ble stående ubesvart til fristen gikk ut.", "Var ikke dette meningen, kan du trekke tilgangen tilbake i hvelvet: [{{url}}]({{{url}}}).", ]), "en": ("{{{grantee_name}}} was granted emergency access", "The deadline passed", [ "{{grantee_name}} has been granted emergency access to {{atype}} your account, because " "the request went unanswered until the deadline passed.", "If that was not intended, you can revoke the access in the vault: [{{url}}]({{{url}}}).", ]), }, } def mot_binær(sti: Path) -> None: """Sammenlign med malene serveren faktisk kan sende. Navnene ligger som tekst i binæren. Kommer det en ny mal i en oppgradering, ville den gått ut med serverens innebygde engelske tekst - og den sier Bitwarden. Da skal bygget stoppe her i stedet for at en kunde oppdager det i innboksen.""" data = sti.read_bytes() finnes = {m.group(1).decode() for m in re.finditer(rb"email/([a-z0-9_]+)(?=[A-Z{])", data)} # Partialene har ingen egen tekst; de innebygde malene bruker dem, våre ikke. finnes -= {"email_header", "email_footer", "email_footer_text"} mangler = finnes - set(MALER) if mangler: raise SystemExit("maler.py: serveren har maler vi ikke har skrevet: " + ", ".join(sorted(mangler))) def skriv(ut: Path) -> int: n = 0 for navn, per_språk in MALER.items(): for språk, (emne, tittel, avsnitt) in per_språk.items(): mappe = ut / språk / "email" mappe.mkdir(parents=True, exist_ok=True) skille = "" (mappe / f"{navn}.hbs").write_text( f"{emne}\n{skille}\n{_tekst(avsnitt)}\n\n{BUNN_TEKST[språk]}\n", encoding="utf-8") (mappe / f"{navn}.html.hbs").write_text( f"{emne}\n{skille}\n" + RAMME.format( lang=HTML_LANG[språk], tittel=tittel, kropp=_html(avsnitt), bunn=BUNN[språk]) + "\n", encoding="utf-8") n += 2 return n def sjekk() -> None: feil = [] for navn, per_språk in MALER.items(): if set(per_språk) != set(SPRÅK): feil.append(f"{navn}: mangler språk {set(SPRÅK) - set(per_språk)}") for språk, (emne, tittel, avsnitt) in per_språk.items(): alt = " ".join([emne, tittel, *avsnitt]) # Vaultwardens eget navn skal aldri stå i en e-post fra oss. if "vaultwarden" in alt.lower(): feil.append(f"{navn}/{språk}: sier Vaultwarden") # Bitwarden er navnet på appen kunden installerer, og skal bare # stå der vi faktisk viser til appen eller utvidelsen. for treff in re.finditer(r"Bitwarden(\S*)", alt): if treff.group(1) not in ("-appen", "-utvidelsen", ""): feil.append(f"{navn}/{språk}: uventet bruk av Bitwarden") if alt.count("{{") != alt.count("}}"): feil.append(f"{navn}/{språk}: ubalanserte klammer") if not avsnitt: feil.append(f"{navn}/{språk}: ingen tekst") if feil: raise SystemExit("maler.py: " + "; ".join(feil)) if __name__ == "__main__": sjekk() if len(sys.argv) > 2 and sys.argv[1] == "--mot": mot_binær(Path(sys.argv[2])) print("maler.py: alle malene serveren kan sende er dekket") raise SystemExit(0) if len(sys.argv) > 1: antall = skriv(Path(sys.argv[1])) print(f"maler.py: skrev {antall} filer i {len(SPRÅK)} språk") else: print(f"maler.py: {len(MALER)} maler, {len(SPRÅK)} språk - alt i orden")