PO4A.1P(1) User Contributed Perl Documentation PO4A.1P(1) NOME po4a - atualizar ambos os ficheiros PO e documentos traduzidos numa so vez SINOPSE po4a [options] config_file DESCRICAO po4a (PO for anything) facilita a manutencao de traducao da documentacao a usar as ferramentas classicas do gettext. A caracteristica principal do po4a e que ele dissocia a traducao do conteudo da estrutura documental. Consulte a pagina po4a(7) para uma introducao suave a este projeto. Upon execution, po4a parses all documentation files specified in its configuration file. It updates the PO files (containing the translation) to reflect any change to the documentation, and produce a translated documentation by injecting the content's translation (found in the PO files) into the structure of the original master document. At first, the PO files only contain the strings to translate from the original documentation. This file format allows the translators to manually provide a translation for each paragraph extracted by po4a. If the documentation is modified after translation, po4a marks the corresponding translations as "fuzzy" in the PO file to request a manual review by the translators. The translators can also provide so- called "addendum", that are extra content stating for example who did the translation and how to report bugs. master documents ---+---->-------->---------+ (doc authoring) | | V (po4a executions) >-----+--> translated | | | documents existing PO files -->--> updated PO files >-+ | ^ | | | V | +----------<---------<-------+ ^ (manual translation process) | | addendum -->--------------------------------------+ The workflow of po4a is asynchronous, as suited to open-source projects. The documentation writers author the master documents at their own pace. The translators review and update the translations in the PO files. The maintainers rerun po4a on need, to reflect any change to the original documentation to the PO files, and to produce updated documentation translations, by injecting the latest translation into the latest document structure. By default, a given translated document is produced when at least 80% of its content is translated. The untranslated text is kept in the original language. The produced documentation thus mixes languages if the translation is not complete. You can change the 80% threshold with the --keep option described below. Note however that discarding translations as soon as they are not 100% may be discouraging for the translators whose work will almost never be shown to the users, while showing "translations" that are too incomplete may be troubling for the end users. Storing the translated documentation files in the version control system is probably a bad idea, since they are automatically generated. The precious files are the PO files, that contain the hard work of your fellow translators. Also, some people find it easier to interact with the translators through an online platform such as weblate, but this is naturally fully optional. Quick start tutorial Let's assume you maintain a program named foo which has a man page man/foo.1 written in English (the bridge language in most open-source projects, but po4a can be used from or to any language). Some times ago, someone provided a German translation named man/foo.de.1 and disappeared. This is a problem because you just got a bug report saying that your documentation contains a gravely misleading information that must be fixed in all languages, but you don't speak German so you can only modify the original, not the translation. Now, another contributor wants to contribute a translation to Japanese, a language that you don't master either. It is time to convert your documentation to po4a to solve your documentation maintenance nightmares. You want to update the doc when needed, you want to ease the work of your fellow translators, and you want to ensure that your users never see any outdated and thus misleading documentation. The conversion includes two steps: setup the po4a infrastructure, and convert the previous German translation to salvage the previous work. This latter part is done using po4a-gettextize, as follows. As detailed in the documentation of po4a-gettextize(1), this process rarely fully automatic, but once it's done, the de.po file containing the German translation can be integrated in your po4a workflow. po4a-gettextize --format man --master foo.1 --localized foo.de.1 --po de.po Let's now configure po4a. With the appropriate file layout, your configuration file could be as simple as this: [po_directory] man/po4a/ [type: man] man/foo.1 $lang:man/translated/foo.$lang.1 It specifies that all PO files (containing the work of the translators) are in the man/po4a/ directory, and that you have only one master file, man/foo.1. If you had several master files, you would have several lines similar to the second one. Each such line also specify where to write the corresponding translation files. Here, the German translation of man/foo.1 is in man/translated/foo.de.1. The last thing we need to complete the configuration of po4a is a POT file containing the template material that should be used to start a new translation. Simply create an empty file with the .pot extension in the specified po_directory (e.g. man/po4a/foo.pot), and po4a will fill it with the expected content. Here is a recap of the files in this setup: man/ | foo.1 <- The original man page, in English | po4a/ | | de.po <- The German PO translation, from gettextization | | foo.pot <- The POT template of future translations (empty at first) | translated/ <- Directory where the translations will be created po4a.cfg <- The configuration file Once setup, executing po4a will parse your documentation, update the POT template file, use it to update the PO translation files, and use them to update the documentation translation files. All in one command: po4a --verbose po4a.cfg This is it. po4a is now fully configured. Once you've fixed your error in man/foo.1, the offending paragraph in the German translation will be replaced by the fixed text in English. Mixing languages is not optimal, but it's the only way to remove errors in translations that you don't even understand, and ensure that the content presented to the users is never misleading. Updating the German translation is also much easier in the corresponding PO file, so the language mix-up may not last long. Finally, when a Japanese translator wants to contribute a new translation, she should rename the foo.pot into ja.po and complete the translation. Once you have this file, just drop it in man/po4a/po/. A translated page will appear as man/translated/foo.ja.1 (provided that enough content is translated) when you run po4a again. OPCOES -k, --keep Limite minimo de percentagem de traducao para manter (p.ex. escrever) o ficheiro resultante (predefinicao: 80). P.ex. por predefinicao, ficheiros tem que ser traduzidos pelo menos a 80% para serem escritos no disco. -w, --width Column at which we should wrap the resulting file if the format supports it (default: 76). -h, --help Mostrar uma pequena mensagem de ajuda. -M, --master-charset Conjunto de carateres dos ficheiros a conter os documentos a traduzir. Note que todos os ficheiros mestres devem ter o mesmo conjunto de carateres. -L, --localized-charset Carrossel dos ficheiros que contem os documentos localizados. Note que todos os documentos traduzidos utilizarao o mesmo conjunto de caracteres. -A, --addendum-charset Conjunto de carateres da adenda. Note-se que todas as adendas devem estar no mesmo conjnto de carateres. -V, --version Mostrar a versao do script e sair. -v, --verbose Aumentar o detalhe do programa. -q, --quiet Diminuir o detalhe do programa. -d, --debug Saida de alguma informacao de depuracao. -o, --option Opcao/coes adicional/ais para passar ao plugin de formato. Veja a documentacao de cada plugin para mais informacoes sobre as opcoes validas e os significados deles. Por exemplo, poderia passar '-o tablecells' para o analisador AsciiDoc, enquanto o analisador de texto aceitaria '-o tabs=split'. -f, --force Gera sempre os ficheiros POT e PO, mesmo se po4a considera que nao e necessario. O comportamento predefinido (quando --force nao e especificado) e o seguinte: Se o ficheiro POT ja existe, ele e regenerado se um documento mestre ou o ficheiro de configuracao e mais recente (a menos que --no-update seja fornecido). O ficheiro POT tambem e escrito num documento temporario e po4a verifica que as mudancas sao realmente necessarias. Alem disso, uma traducao e regenerada somente se o documento mestre dela, o ficheiro PO, uma das suas adendas ou o ficheiro de configuracao e mais recente. Para evitar tentar regenerar traducoes que nao passam no teste de limite (ver --keep), um ficheiro com a extensao .po4a-stamp pode ser criado (ver --stamp). Se um documento mestre inclui ficheiros, deve usar a flag --force porque o tempo de modificacao dos ficheiros incluidos nao sao tomados em conta. Os ficheiros PO sao sempre regenerados com base em POT com msgmerge -U. --stamp Diz po4a para criar ficheiros de informacao quando uma traducao nao e gerada porque nao atingiu o limiar. Esses ficheiros de informacao sao nomeados de acordo com o documento traduzido esperado, com a extensao .po4a-stamp. Nota: Isso so ativa a criacao dos ficheiros .po4a-stamp. Os ficheiros de informacao sao sempre utilizados se existirem e sao removidos com --rm-translations ou quando o ficheiro e finalmente traduzido. --no-translations Nao gerar os documentos traduzidos, apenas atualizar os ficheiros POT e PO. --no-update Nao altere os ficheiros POT e PO, apenas a traducao pode ser atualizada. --keep-translations Mantem os ficheiros de traducao existentes mesmo se a traducao nao atingir o limite especificado por --keep. Essa opcao nao cria ficheiros de traducao com pouco conteudo, mas vai gravar traducoes existentes que cujo nivel decai devido a alteracoes nos ficheiros de mestre. ATENCAO: esta opcao muda o comportamento do po4a de uma maneira bastante drastica: os seus ficheiros traduzidos nao serao atualizados ate que a traducao melhore. Use esta opcao somente se preferir enviar uma documentacao traduzida desatualizada em vez de enviar apenas uma documentacao nao traduzida precisa. --rm-translations Remover os ficheiros traduzidos (implica --no-translations). --no-backups Esta flag nao faz nada desde 0.41 e pode ser removida em versoes posteriores. --rm-backups Esta flag nao faz nada desde 0.41 e pode ser removida em versoes posteriores. --translate-only translated-file Traduzir apenas o ficheiro especificado. Pode ser util para acelerar o processamento se um ficheiro de configuracao contem muitos ficheiros. Note que o que esta opcao faz nao e possivel atualizar ficheiros PO e POT. Esta opcao pode ser usada varias vezes. --variable var=value Definir uma variavel que sera expandida no ficheiro de configuracao po4a.Cada ocorrencia de $(var) sera substituida por value. Esta opcao pode ser usada varias vezes. --srcdir SRCDIR Definir o diretorio de base para todos os documentos de entrada especificados no ficheiro de configuracao po4a. Se destdir e srcdir forem especificados, os ficheiros de entrada serao pesquisados nos diretorios seguintes, em ordem: destdir, o diretorio atual e srcdir. Os ficheiros de saida sao gravados em destdir, se forem especificados, ou no diretorio atual. --destdir DESTDIR Definir o diretorio base para todos os documentos de saida especificados no ficheiro de configuracao po4a (veja --srcdir acima). Opcoes que modificam o cabecalho do POT --porefs type Especifica o formato de referencia. O argumento tipo pode ser um de: never para nao produzir qualquer referencia, file para especificar o ficheiro sem o numero da linha, counter para substituir os numeros de linha a aumentar o contador e full para incluir referencias completas. (predefinicao: full). --wrap-po no|newlines|number (predefinicao: 76) Especifica como o ficheiro po deve ter a quebra de linha. Isso permite escolher entre ficheiros que tem boa quebra de linha, mas que podem levar a conflitos de git ou ficheiros que sao mais faceis de manipular automaticamente, mas mais dificeis de ler para humanos. Historicamente, o pacote gettext reformatou os ficheiros po na 77a coluna para questoes cosmeticas. Esta opcao especifica o comportamento de po4a. Se for definido como um valor numerico, o po4a quebrara linha do ficheiro po apos esta coluna e apos novas linhas no conteudo. Se for definido como newlines, o po4a dividira apenas o msgid e o msgstr apos as novas linhas no conteudo. Se for definido como no, o po4a nao quebrara linha do ficheiro po. Os comentarios de referencia tem sempre as linhas quebradas pelas ferramentas do gettext que usamos internamente. Observe que esta opcao nao afeta a maneira como o msgid e o msgstr sofrem quebra de linhas ou seja, como os caracteres de nova linha sao adicionados ao conteudo dessas cadeias. --master-language Idioma dos ficheiros fonte que contem os documentos a traduzir. Note que todos os documentos principais devem usar o mesmo idioma. --msgid-bugs-address e-mail@endereco Definir o endereco do relatorio para msgid bugs. Por predefinicao, os ficheiros POT criados nao tem campos Report-Msgid-bugs-To. --copyright-holder string Definir o titular dos direitos de autor no cabecalho POT. O valor predefinido e " Free Software Foundation, Inc." --package-name string Definir o nome do pacote para o cabecalho POT. A predefinicao e "PACKAGE". --package-version string Definir o nome do pacote para o cabecalho POT. A predefinicao e "VERSION". Opcoes para modificar os ficheiros PO --msgmerge-opt options Opcoes extra para msgmerge(1). Nota: $lang sera estendida ao idioma atual. --no-previous Esta opcao remove --previous a partir das opcoes passadas para B Isto e necessario para suportar versoes do gettext mais cedo do que 0.16. --previous Esta opcao acrescenta --previous as opcoes passadas para msgmerge. Isso requer gettext 0.16 ou posterior e e ativado por predefinicao. CONFIGURATION FILE po4a espera um ficheiro de configuracao como argumento. Este ficheiro deve conter os elementos seguintes: o O caminho aos ficheiros PO e a lista de idiomas existentes no projeto; o Opcionalmente, algumas opcoes globais e os chamados aliases de configuracao que sao usados como modelos para configurar ficheiros mestres individuais ; o A lista de cada ficheiro mestre a traduzir, juntamente com parametros especificos. Todas as linhas contem um comando entre colchetes, seguido pelos seus parametros. Os comentarios comecam com o caractere "#" e correm ate ao fim da linha. Pode escapar do fim da linha para espalhar um comando por varias linhas. Alguns exemplos completos sao apresentados nesta pagina, enquanto outros exemplos encontram-se no diretorio "t/cfg" da distribuicao fonte. Encontrar os ficheiros PO e POT A solucao mais simples e fornecer o caminho aos ficheiros POT e PO, da maneira seguinte: [po4a_paths] man/po/project.pot de:man/po/de.po fr:man/po/fr.po Isto especifica o caminho ao ficheiro POT primeiro e, em seguida, os caminhos aos ficheiros PO alemaes e franceses. A mesma informacao pode ser escrita da seguinte forma para reduzir o risco de erros de copia/colagem: [po4a_langs] fr de [po4a_paths] man/po/project.pot $lang:man/po/$lang.po O componente $lang e expandido automaticamente a usar a lista de idiomas fornecidos, a reduzir o risco de erro de copiar/colar quando um novo idioma e adicionado. Pode compactar ainda mais as mesmas informacoes a fornecer apenas o caminho ao diretorio que contem o seu projeto de traducao, como a seguir. [po_directory] man/po/ O diretorio fornecido deve conter um conjunto de ficheiros PO, cada um chamado XX.po, o "XX" a ser o ISO 631-1 do idioma utilizado neste ficheiro. O diretorio tambem deve conter um unico ficheiro POT, com a extensao ".pot" file. Para a primeira execucao, este ficheiro pode estar vazio, mas deve existir (po4a nao pode adivinhar o nome a usar antes da extensao). Note que deve escolher apenas um de "po_diretorio" e "po4a_caminhos". O primeiro ("po_directorio") e mais compacto, reduz ainda mais o risco de erro de copiar/colar, mas obriga-o a usar a estrutura de projeto e os nomes de ficheiro esperados. O segundo ("po4a_paths") e mais explicito, provavelmente mais legivel e e aconselhado quando configura o seu primeiro projeto com o po4a. Ficheiros PO centralizados ou divididos? Por predefinicao, o po4a produz um unico ficheiro PO por idioma de destino, a conter todo o conteudo do seu projeto de traducao. Conforme o seu projeto cresce, o tamanho desses ficheiros pode tornar-se problematico. Ao utilizar weblate, e possivel especificar prioridades para cada segmento de traducao (isto e, msgid), de modo que os importantes sejam traduzidos primeiro. Mesmo assim, algumas equipas de traducao preferem dividir o conteudo em varios ficheiros. Para ter um ficheiro PO por ficheiro mestre, basta usar a cadeia $master no nome dos ficheiros PO na linha "[po4a_paths]", da maneira seguinte. [po4a_paths] doc/$master/$master.pot $lang:doc/$master/$lang.po With this line, po4a will produce separate POT and PO files for each document to translate. For example, if you have 3 documents and 5 languages, this will result in 3 POT files and 15 PO files. These files are named as specified on the "po4a_paths" template, with $master substituted to the basename of each document to translate. In case of name conflict, you can specify the POT file to use as follows, with the "pot=" parameter. This feature can also be used to group several translated files into the same POT file. The following example only produces 2 POT files: l10n/po/foo.pot (containing the material from foo/gui.xml) and l10n/po/bar.pot (containing the material from both bar/gui.xml and bar/cli.xml). [po4a_langs] de fr ja [po4a_paths] l10n/po/$master.pot $lang:l10n/po/$master.$lang.po [type: xml] foo/gui.xml $lang:foo/gui.$lang.xml pot=foo [type: xml] bar/gui.xml $lang:bar/gui.$lang.xml pot=bar [type: xml] bar/cli.xml $lang:bar/cli.$lang.xml pot=bar No modo split, po4a constroi um compendio temporario durante a atualizacao do PO, para compartilhar as traducoes entre todos os ficheiros do PO. Se dois ficheiros PO tiverem traducoes diferentes para a mesma cadeia, po4a marcara esta cadeia como difusa e enviara ambas as traducoes em todos os ficheiros PO a conter esta cadeia. Quando o tradutor nao se deixa abater, a traducao e automaticamente utilizada em todos os ficheiros PO. Especificando os documentos para traduzir You must also list the documents that should be translated. For each master file, you must specify the format parser to use, the location of the translated document to produce, and optionally some configuration. File names should be quoted or escaped if they contain spaces. Here is an example: [type: sgml] "doc/my stuff.sgml" "fr:doc/fr/mon truc.sgml" de:doc/de/mein\ kram.sgml [type: man] script fr:doc/fr/script.1 de:doc/de/script.1 [type: docbook] doc/script.xml fr:doc/fr/script.xml \ de:doc/de/script.xml Mas, novamente, essas linhas complexas sao dificeis de ler e modificar, por exemplo, ao adicionar um novo idioma. E muito mais simples reorganizar as coisas a usar o modelo $lang da maneira seguinte: [type: sgml] doc/my_stuff.sgml $lang:doc/$lang/my_stuff.sgml [type: man] script.1 $lang:po/$lang/script.1 [type: docbook] doc/script.xml $lang:doc/$lang/script.xml Especificar opcoes Ha dois tipos de opcoes: opcoes do po4a sao valores predefinidos para as opcoes de linha de comando po4a enquanto opcoes de formato sao usadas para alterar o comportamento dos analisadores de formato. Como uma opcoes do po4a, pode, por exemplo, especificar no seu ficheiro de configuracao que o valor predefinido do parametro de linha de comando --keep e de 50% em vez de 80%. Opcoes de formato estao documentadas na pagina especifica de cada modulo de analise, por exemplo, Locale::Po4a::Xml(3pm). Pode, por exemplo, passar nostrip para o analisador XML para nao remover os espacos ao redor das cadeias extraidas. Pode passar essas opcoes a um ficheiro mestre especifico ou mesmo a uma traducao especifica desse ficheiro, a usar "opt:" e "opt_XX:" para o idioma "XX". No exemplo a seguir, a opcao nostrip e passada ao analisador XML (para todos os idiomas), enquanto o limite sera reduzido a 0% para a traducao em frances (que, portanto, e sempre mantida). [type:xml] toto.xml $lang:toto.$lang.xml opt:"-o nostrip" opt_fr:"--keep 0" De qualquer forma, esses blocos de configuracao devem estar localizados no final da linha. A declaracao dos ficheiros deve vir primeiro, depois o adendo, se houver (veja abaixo) e somente as opcoes. O agrupamento de pedacos de configuracao nao e muito importante, pois os elementos sao internamente concatenados como cadeias. Os exemplos a seguir sao todos equivalentes: [type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20" opt:"-o nostrip" opt_fr:"--keep 0" [type:xml] toto.xml $lang:toto.$lang.xml opt:"--keep 20 -o nostrip" opt_fr:"--keep 0" [type:xml] toto.xml $lang:toto.$lang.xml opt:--keep opt:20 opt:-o opt:nostrip opt_fr:--keep opt_fr:0 Observe que as opcoes especificas do idioma nao sao usadas ao criar o ficheiro POT. Por exemplo, e impossivel passar nostrip para o analisador apenas ao criar a traducao em frances, porque o mesmo ficheiro POT e usado para atualizar todos os idiomas. Portanto, as unicas opcoes que podem ser especificas do idioma sao as usadas na producao da traducao, como a opcao "--keep". Aliases de configuracao Para passar as mesmas opcoes para varios ficheiros, o melhor e definir um alias de tipo da maneira seguinte. No proximo exemplo, "--keep 0" e passado para todas as traducoes em italiano a usar este tipo "test", que e uma extensao do tipo "man". [po4a_alias:test] man opt_it:"--keep 0" [type: test] man/page.1 $lang:man/$lang/page.1 Tambem pode estender um tipo existente de reutilizar o mesmo nome ao alias da forma seguinte. Este nao e interpretada como uma erronea definicao recursiva. [po4a_alias:man] man opt_it:"--keep 0" [type: man] man/page.1 $lang:man/$lang/page.1 Opcoes predefinidas globais Tambem pode usar as linhas "[options]" para definir opcoes que devem ser usadas para todos os ficheiros, independentemente do tipo dele. [options] --keep 20 --option nostrip Como nas opcoes da linha de comando, pode abreviar os parametros passados no ficheiro de configuracao: [options] -k 20 -o nostrip Prioridades das opcoes As opcoes de todas as fontes sao concatenadas, a garantir que os valores predefinidos possam ser facilmente substituidos por opcoes mais especificas. A ordem e a seguinte: o As linhas "[options]" fornecem valores predefinidos que podem ser substituidos por qualquer outra fonte. o Os aliases de tipo sao entao usados. As configuracoes especificas do idioma substituem as aplicaveis a todos os idiomas. o As configuracoes especificas de um determinado ficheiro mestre substituem as predefinidas e as provenientes do alias de tipo. Neste caso, as configuracoes especificas do idioma tambem substituem as globais. o Finalmente, os parametros fornecidos na linha de comando po4a substituem todas as configuracoes do ficheiro de configuracao. Exemplo Aqui esta um exemplo a mostrar como citar os espacos e aspas: [po_directory] man/po/ [options] --master-charset UTF-8 [po4a_alias:man] man opt:"-o \"mdoc=NAME,SEE ALSO\"" [type:man] t-05-config/test02_man.1 $lang:tmp/test02_man.$lang.1 \ opt:"-k 75" opt_it:"-L UTF-8" opt_fr:--verbose Adendo: adicionar conteudo na traducao Se deseja adicionar uma seccao adicional a traducao, por exemplo, para dar merito ao tradutor, e necessario definir um adendo a linha que define o seu ficheiro mestre. Consulte a pagina po4a(7) para obter mais detalhes sobre a sintaxe dos ficheiros de adendo. [type: pod] script fr:doc/fr/script.1 \ add_fr:doc/l10n/script.fr.add Tambem pode usar modelos de idioma da maneira seguinte: [type: pod] script $lang:doc/$lang/script.1 \ add_$lang:doc/l10n/script.$lang.add Se um adendo nao se aplicar, a traducao sera descartada. Modificadores para a declaracao de adendo Os modificadores de adendo podem simplificar o ficheiro de configuracao no caso em que nem todos os idiomas fornecem um adendo ou quando a lista de adendos muda de um idioma ao outro. O modificador e um unico caractere localizado antes do nome do ficheiro. ? Incluir addendum_path se este ficheiro existir, caso contrario nao fazer nada. @ addendum_path nao e uma adenda regular, mas um ficheiro a conter uma lista de adendas, uma por linha. Cada adenda pode ser precedida de modificadores. ! addendum_path e descartado, nao e carregado e nao sera carregado por qualquer especificacao de adenda. O seguinte inclui um adendo em qualquer idioma, mas so se existir. Nenhum erro sera relatado se o adendo nao existir. [type: pod] script $lang:doc/$lang/script.1 add_$lang:?doc/l10n/script.$lang.add O seguinte inclui uma lista de adendos para cada idioma: [type: pod] script $lang:doc/$lang/script.1 add_$lang:@doc/l10n/script.$lang.add Filtrar as cadeias traduzidas As vezes, deseja ocultar algumas cadeias do processo de traducao. Nesse sentido, pode atribuir um parametro "pot_in" ao seu ficheiro mestre para especificar o nome do ficheiro a ser usado em vez do mestre real ao criar o ficheiro POT. Aqui esta um exemplo: [type:docbook] book.xml \ pot_in:book-filtered.xml \ $lang:book.$lang.xml Com essa configuracao, as cadeias a serem traduzidas serao extraidas do book-filter.xml (que deve ser produzido antes da chamada de po4a) enquanto os ficheiros traduzidos serao compilados do book.xml. Como resultado, qualquer cadeia que faca parte de book.xml, mas nao em book-filter.xml nao sera incluida nos ficheiros PO, a impedir que os tradutores fornecam uma traducao para eles. Portanto, essas cadeias nao serao modificadas ao produzir os documentos traduzidos. Isso naturalmente diminui o nivel de traducao, portanto, pode precisar da opcao "--keep" para garantir que o documento seja produzido de qualquer maneira. VER TAMBEM po4a-gettextize(1), po4a(7). AUTORES Denis Barbier Nicolas Francois Martin Quinson (mquinson#debian.org) DIREITOS DE AUTOR E LICENCA Copyright 2002-2023 by SPI, inc. This program is free software; you may redistribute it and/or modify it under the terms of GPL v2.0 or later (see the COPYING file). perl v5.38.2 2024-06-26 PO4A.1P(1)