diff --git a/data/brushes/vine.txt b/data/brushes/vine.txt new file mode 100644 index 000000000..6cba4b7d7 --- /dev/null +++ b/data/brushes/vine.txt @@ -0,0 +1,3 @@ +Draw with leafy vines. +ca.utf8=Dibuixa amb ceps de fulla. +ja.utf8=葉の茂った蔓を描きます。 diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index 5f37a7a31..842477117 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -6,7 +6,7 @@ Copyright (c) 2002-2024 Various contributors (see below, and AUTHORS.txt) https://tuxpaint.org/ -2024.March.19 (0.9.33) +2024.March.25 (0.9.33) * New Magic Tools: ---------------- * WIP Specular Reflection: Draws a slightly blurred, wavy, and @@ -32,6 +32,11 @@ https://tuxpaint.org/ * Other Improvements: ------------------- + * WIP Support for brush descriptions, including localization. + (Adds a new "tuxpaint-brushes.pot" & PO files.) + Management scripts based on those found in `tuxpaint-stamps`.) + Bill Kendrick + * Make screen refresh more snappy on macOS. Mark Kim diff --git a/txt-po/README.txt b/txt-po/README.txt new file mode 100644 index 000000000..f6e657f25 --- /dev/null +++ b/txt-po/README.txt @@ -0,0 +1,11 @@ +In this directory you'll find scripts for converting description files +(e.g. brushes) to and from gettext PO files, for use by translators. + +Run the following two scripts (in this order) everytime a description +is added or changed, or a translation (PO file) is updated: + +createpo.sh +createtxt.sh + +Note: Never edit the translations in the .txt files, +or run the .py scripts directly. diff --git a/txt-po/createpo.sh b/txt-po/createpo.sh new file mode 100755 index 000000000..aa55a3725 --- /dev/null +++ b/txt-po/createpo.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +# "createpo.sh" for Tux Paint brush description files +# (based on "createpo.sh" from `tuxpaint-stamps`) +# Last modified 2024-03-25 + +# Generate an updated translation template file for the +# descriptions (".pot") based on the main English +# strings found in the text description files ("filename.txt"). +chmod 755 txt2pot.py +./txt2pot.py + +# Unify any duplicate translations in the message catalog (".pot") +msguniq tuxpaint-brushes.pot | fgrep -v '#-#-#-#-#' > temp.tmp && mv -f temp.tmp tuxpaint-brushes.pot + +# Merge the existing translations with the updated ".pot" file +for i in *.po ; do + echo $i + msgmerge --quiet --update --previous --no-wrap --backup=none $i tuxpaint-brushes.pot + # Note: Not using --sort-output, since the POT should be in + # the order of the files themselves (by filename), + # and that's much more useful than sorting by "msgid". +done + +msguniq --no-wrap --to-code=UTF-8 tuxpaint-brushes.pot > temp.tmp && mv -f temp.tmp tuxpaint-brushes.pot + diff --git a/txt-po/createtxt.sh b/txt-po/createtxt.sh new file mode 100755 index 000000000..6a8c53868 --- /dev/null +++ b/txt-po/createtxt.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# "createtxt.sh" for Tux Paint text description files +# (based on "createtxt.sh" from `tuxpaint-stamps`) +# Last modified 2023-03-25 + +set -e + +# Unify any duplicate translations +for i in *.po* ; do + msguniq --no-wrap --to-code=UTF-8 $i > temp.tmp && mv -f temp.tmp $i +done + +# Regenerate the description text files ("filename.txt") +# from translation files (".po") +chmod 755 po2txt.py +./po2txt.py diff --git a/txt-po/po2txt.py b/txt-po/po2txt.py new file mode 100755 index 000000000..c865fe13f --- /dev/null +++ b/txt-po/po2txt.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +# po2txt.py for Tux Paint brush description files +# (based on "po2txt.py" from `tuxpaint-stamps`) +# +# Last modified 2024-03-25 +# +# Updates brush description files ("file.txt"), +# which contain the textual description of a brush +# in English, plus all languages it has been translated to. +# This script reads the various localization (".po") files +# to fill in the translated lines (e.g., "es=Description in Spanish"). +# (Those ".po" files are based on a translation template ".pot" file, +# which is based on the main English strings found in the ".txt" files; +# the script "txt2pot.py" generates the ".pot" file.) + +import sys, os, glob, string + +def lister(dummy, dirname, filesindir): + filesindir.sort() + for fname in filesindir: + fnlist = os.path.splitext(fname) + # change the file ext name to .txt whan ready. + if fnlist[1] == ".txt": + print(">> " + dirname + "/" + fname) + # open txt file to read. + txtFile = open(os.path.join(dirname, fname), 'rb') + # get first line : the description. + searchText_unescaped = txtFile.readline()[:-1] + searchText_unescaped = searchText_unescaped.decode() + print(searchText_unescaped) + searchText = searchText_unescaped.replace('"', '\\"') + fullsearchText = "msgid \"" + searchText + "\"\n" + ## print("Searching for <<" + searchText_unescaped + ">>") + if searchText in msgidsList: + # reopen txt file to write. + txtFile = open(os.path.join(dirname, fname), 'wb') + # write back the description. + ## print("Writing back " + searchText_unescaped) + txtFile.write((searchText_unescaped + '\n') .encode()) + # get locale code from the sorted localeList. + for locale in localeList: + # seek position to the PO file's beginning. + localeFiles[locale].seek(0) + # search the searchText. + noFuzzy = 1 + line = localeFiles[locale].readline() + line = line.decode() + while fullsearchText != line: + # determine fuzzy. + if line[:8] == '#, fuzzy': + noFuzzy = 0 + else: + if line[:6] == 'msgstr': + noFuzzy = 1 + line = localeFiles[locale].readline() + line = line.decode() + # if matched, read the next line, will be translated description. + line = localeFiles[locale].readline() + line = line.decode() + if line[:6] == 'msgstr' and line[8:-2] != "" and noFuzzy: + line_sub = line[8:-2] + translation_unescaped = line_sub.replace('\\"', '"') + txtFile.write(locale.encode() + ".utf8=".encode() + translation_unescaped.encode() + "\n" . encode()) + txtFile.close() + +def parsePOT(potFileName): + try: + potFile = open(potFileName, 'rb') + except(IOError, e): + print("Unable to open the file:" , potFileName, e) + print(potFileName) + # pass the pot header + while len(potFile.readline()) > 1 : pass + # parse filename and msgid + for line in potFile.readlines(): + line = line.decode() + if line[:5] == 'msgid': + # get msgid. + msgidsList.append(line[7:-2]) + # if msgid has more one line, + # we need change the way. + potFile.close() + +if __name__ == '__main__': + localeFiles = {} + localeList = [] + msgidsList = [] + + # parse pot file to fill msgidsList. + parsePOT('./tuxpaint-brushes.pot') + + poFileNames = glob.glob('*.po') + for poFileName in poFileNames: + # add locale to localeList. + localeList.append(poFileName[17:-3]) + # add po file to localeFiles. + localeFiles[poFileName[17:-3]] = open(poFileName, 'rb') + + # sort locale code. + localeList.sort() + + # walk around all .txt files. + for root, dirs, files in sorted(os.walk('../data/brushes')): + lister(None, root, sorted(files)) + + # close all po files. + for locale in localeList: + localeFiles[locale].close() diff --git a/txt-po/tuxpaint-brushes-ca.po b/txt-po/tuxpaint-brushes-ca.po new file mode 100644 index 000000000..fc28e0284 --- /dev/null +++ b/txt-po/tuxpaint-brushes-ca.po @@ -0,0 +1,3 @@ +#: ../data/brushes/vine.txt +msgid "Draw with leafy vines." +msgstr "Dibuixa amb ceps de fulla." diff --git a/txt-po/tuxpaint-brushes-ja.po b/txt-po/tuxpaint-brushes-ja.po new file mode 100644 index 000000000..0a83fd5ea --- /dev/null +++ b/txt-po/tuxpaint-brushes-ja.po @@ -0,0 +1,17 @@ +msgid "" +msgstr "" +"Project-Id-Version: tuxpaint-brushes\n" +"Report-Msgid-Bugs-To: tuxpaint-i18n@lists.sourceforge.net\n" +"POT-Creation-Date: 2024-01-26 07:52:18+0000\n" +"PO-Revision-Date: 2024-01-27 11:07+0900\n" +"Last-Translator: Shin-ichi TOYAMA \n" +"Language-Team: Japanese \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.2\n" + +#: ../data/brushes/vine.txt +msgid "Draw with leafy vines." +msgstr "葉の茂った蔓を描きます。" diff --git a/txt-po/tuxpaint-brushes.pot b/txt-po/tuxpaint-brushes.pot new file mode 100644 index 000000000..33e59e94d --- /dev/null +++ b/txt-po/tuxpaint-brushes.pot @@ -0,0 +1,18 @@ +# Tuxpaint-brushes [Language] translation. +# Copyright (C) 2024-2024 +# This file is distributed under the same license as the Tuxpaint package. +# [Translator's name] , 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: tuxpaint-brushes\n" +"Report-Msgid-Bugs-To: tuxpaint-i18n@lists.sourceforge.net\n" +"MIME-Version: 1.0\n" +"POT-Creation-Date: 2024-03-26 03:54:30+0000\n" +"PO-Revision-Date: \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../data/brushes/vine.txt +msgid "Draw with leafy vines." +msgstr "" diff --git a/txt-po/txt2pot.py b/txt-po/txt2pot.py new file mode 100755 index 000000000..c2f3ca4c5 --- /dev/null +++ b/txt-po/txt2pot.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +# txt2pot.py for Tux Paint text description files +# (based on "txt2pot.py" from `tuxpaint-stamps`) +# +# Last modified 2024-03-25 +# +# Updates description localization template (".pot") +# to capture any new strings, or changes to the main English +# translations, found within the brush description files +# ("filename.txt"), which contain both the textual description +# of the brush in English, plus all languages it has been translated to. +# (As generated by "po2txt.py"). + +import sys, os, string +from time import gmtime, strftime + +poHeader="""# Tuxpaint-brushes [Language] translation. +# Copyright (C) 2024-""".encode()+\ +strftime("%Y", gmtime()).encode()+"\n".encode()+\ +"""# This file is distributed under the same license as the Tuxpaint package. +# [Translator's name] , """.encode()+\ +strftime("%Y", gmtime()).encode()+"\n"\ +"""# +msgid "" +msgstr "" +"Project-Id-Version: tuxpaint-brushes\\n" +"Report-Msgid-Bugs-To: tuxpaint-i18n@lists.sourceforge.net\\n" +"MIME-Version: 1.0\\n" +"POT-Creation-Date: """.encode()+\ +strftime("%Y-%m-%d %H:%M:%S+0000", gmtime()).encode()+"\\n\"\n"\ +""""PO-Revision-Date: \\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +""".encode() +localeList = [] + + +def lister(poLocale, dirname, filesindir): + filesindir.sort() + for fname in filesindir: + fnlist = os.path.splitext(fname) + if fnlist[1] == ".txt": + print(dirname + "/" + fname) + txtFile = open(os.path.join(dirname, fname), 'rb') + # write filename down follow mark('#'). + poFile.write(('\n#: ' + dirname + '/' + fnlist[0] + fnlist[1] + '\n').encode()) + # write description in msgid. + line = txtFile.readline().decode() + if line[-1:] == "\n" : + line = line[:-1] + line = line.replace('"', '\\"') + print(" ==> <<" + line + ">>") + poFile.write(('msgid "' + line + '"\n').encode()) + localeString = "" + for lineEnc in txtFile.readlines(): + line = lineEnc.decode() + # replace " to \" + line = line.replace('"', '\\"') + splitup = line.find('.utf8') + if splitup != -1 and line[0] != '#' : + locale = line[:splitup] + if locale not in localeList: + localeList.append(locale) + if locale == poLocale: + localeString = line[string.find(line, "=") + 1:-1] + txtFile.close() + # write translation or empty string in msgstr. + poFile.write(('msgstr "' + localeString + '"\n').encode()) + +if __name__ == '__main__': + poFile = open('./tuxpaint-brushes.pot', 'wb') + print(">>", poFile.name) + poFile.write(poHeader) + + for root, dirs, files in sorted(os.walk('../data/brushes')): + lister(None, root, sorted(files)) + + poFile.close()