Skip to content

Commit 0064451

Browse files
committed
contrib/abpoutil: new helper script
This helper script aids the creation and maintenance of the PO template file and translated PO catalogs. - contrib: Install helper scripts. - CMakeLists.txt: Likewise, but add the contrib directory as subdirectory instead of adding install() entries directly to make the CMake script managable.
1 parent bdaa47d commit 0064451

File tree

3 files changed

+305
-0
lines changed

3 files changed

+305
-0
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,5 @@ install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/ab4.sh" DESTINATION "${CMAKE_INSTA
168168
install(TARGETS autobuild LIBRARY DESTINATION "${AB_INSTALL_PREFIX}")
169169
install(DIRECTORY arch filters helpers lib pm proc qa sets templates DESTINATION "${AB_INSTALL_PREFIX}")
170170
install(DIRECTORY etc/autobuild DESTINATION "${CMAKE_INSTALL_FULL_SYSCONFDIR}")
171+
172+
add_subdirectory(contrib)

contrib/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set(CONTRIB_SCRIPTS
2+
abpoutil
3+
)
4+
5+
install(PROGRAMS ${CONTRIB_SCRIPTS} TYPE BIN)

contrib/abpoutil

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
#!/bin/bash
2+
# shellcheck disable=SC2268,SC1090
3+
4+
usage() {
5+
cat >&2 << EOF
6+
Usage: $0 ACTION [...] DEFINES
7+
8+
Perform operations for PO files in the ABBS tree. You can generate, create,
9+
update and check the PO template and PO catalog files.
10+
11+
Actions:
12+
generate Generate a PO Template from \${GETTEXT_SRCS[@]}
13+
defined in the autobuild/defines file.
14+
create LANG Create an empty PO file for LANG, from the template
15+
file \$GETTEXT_DOMAIN.pot.
16+
update Update the PO Template file and all translations.
17+
check Check if there's any fuzzy messages in the translated
18+
PO files.
19+
20+
Positional Arguments:
21+
- DEFINES:
22+
Path to the defines file.
23+
24+
NOTES:
25+
- This script only works with one defines file per invocation.
26+
EOF
27+
}
28+
29+
action="$1"
30+
shift
31+
defines=defines
32+
ab_dir=.
33+
po_dir="$ab_dir"/po
34+
35+
# Utility functions.
36+
37+
# Transform the given string to variable names for indirect expansion.
38+
# For example:
39+
# "a/b/c/d-123 456" -> "a_b_c_d_123_456"
40+
# Then it will be used as:
41+
# var="$(transform_varname "a/b/c/d-123 456")"
42+
# x=${!var}
43+
transform_varname() {
44+
local str="$1" ret
45+
ret="${str//[^0-9a-zA-Z_]/_}"
46+
# Variable names should start with a letter or underscore.
47+
# Strip a digit from the string to see if it remains the same.
48+
if [ "${ret#[0-9]}" = "${ret}" ] ; then
49+
echo "$ret"
50+
else
51+
echo "_$ret"
52+
fi
53+
}
54+
55+
# Perform QA checks before running the specified action.
56+
check_defines() {
57+
local nodef=() nosrc=()
58+
local _var _path
59+
# Check required variables.
60+
for i in GETTEXT_DOMAIN GETTEXT_LINGUAS GETTEXT_SRCS ; do
61+
_var="${i}[*]"
62+
if [ "x${!_var}" = "x" ] ; then
63+
nodef+=("$i")
64+
fi
65+
done
66+
if [ "${#nodef[@]}" -ge 1 ] ; then
67+
echo -e "ERROR: Missing Gettext related definition in defines:\n\t${nodef[*]}" >&2
68+
exit 1
69+
fi
70+
if [ "${GETTEXT_DOMAIN##aosc-}" = "$GETTEXT_DOMAIN" ] ; then
71+
echo "ERROR: GETTEXT_DOMAIN must begin with \`aosc-', got \`$GETTEXT_DOMAIN'." >&2
72+
exit 1
73+
fi
74+
for src in "${GETTEXT_SRCS[@]}" ; do
75+
# Check if this soruce file actually exists
76+
_path="$ab_dir"/"$src"
77+
if ! [ -f "$_path" ] ; then
78+
nosrc+=("autobuild/$src")
79+
fi
80+
done
81+
if [ "${#nosrc[@]}" -gt 0 ] ; then
82+
echo "ERROR: The following source file(s) defined in GETTEXT_SRCS do not exist:" >&2
83+
for src in "${nosrc[@]}" ; do
84+
echo "- $src" >&2
85+
done
86+
echo "Please note that all sources defined here must stay within the autobuild directory." >&2
87+
exit 1
88+
fi
89+
}
90+
91+
try_read_defines() {
92+
defines="$1"
93+
if [ x"$defines" = "x" ] ; then
94+
if [ -e "$PWD"/defines ] ; then
95+
defines="$PWD"/defines
96+
elif [ -e "$PWD"/autobuild/defines ] ; then
97+
defines="$PWD"/autobuild/defines
98+
elif [ -e "$PWD"/../defines ] ; then
99+
defines="$(realpath "$PWD"/..)"/defines
100+
else
101+
echo "ERROR: no defines file is found under the current directory." >&2
102+
usage
103+
exit 1
104+
fi
105+
elif [ ! -f "$defines" ] ; then
106+
echo "ERROR: Specified file is not found or is not a regular file." >&2
107+
usage
108+
exit 1
109+
elif [ "$(basename "$defines")" != "defines" ] ; then
110+
echo "ERROR: defines file required, got $defines". >&2
111+
usage
112+
exit 1
113+
fi
114+
ab_dir="$(dirname "$defines")"
115+
po_dir="$ab_dir/po"
116+
echo "-- Using defines file: $defines"
117+
source "$defines"
118+
check_defines
119+
}
120+
121+
preq_check() {
122+
if ! [ -d "$ab_dir"/po ] ; then
123+
echo "ERROR: autobuild/po directory does not exist." >&2
124+
echo "Perhaps you should run \`$0 gen $defines' to create one." >&2
125+
exit 1
126+
fi
127+
# Check if the POT file actually exists.
128+
if [ ! -e "$ab_dir"/po/"$GETTEXT_DOMAIN".pot ] ; then
129+
echo "ERROR: PO Template $GETTEXT_DOMAIN.pot does not exist!" >&2
130+
fi
131+
# Skip the check if we're going to update the POT and PO files.
132+
if [ "x$action" = "xupdate" ] ; then
133+
return 0
134+
fi
135+
# Check if the POT is older than sources.
136+
gettext_mtime_pot=$(stat --printf "%Y" "$po_dir"/"$GETTEXT_DOMAIN".pot)
137+
gettext_mtime_src=0
138+
for src in "${GETTEXT_SRCS[@]}" ; do
139+
_mtime=$(stat --printf "%Y" "$ab_dir"/"$src")
140+
if [ "$_mtime" -gt "$gettext_mtime_src" ] ; then
141+
gettext_mtime_src="$_mtime"
142+
fi
143+
done
144+
if [ "$gettext_mtime_src" -gt "$gettext_mtime_pot" ] ; then
145+
# Sources used to generate the POT is newer than the template file.
146+
echo "ERROR: Source files are newer than the PO Template $GETTEXT_DOMAIN.pot!" >&2
147+
echo "You can run \`$0 update $defines' to update the template file." >&2
148+
fi
149+
}
150+
151+
create_lang() {
152+
local newlang="$1" langcodes=() has_lang=0
153+
if [ "x$1" = "x" ] ; then
154+
echo "ERROR: Invalid usage: Language code required." >&2
155+
return 1
156+
fi
157+
langcodes=("$(locale -a | sed 's|\..*||g' | sort | uniq)")
158+
for l in "${langcodes[@]}" ; do
159+
if [ "x$l" = "x$lang" ] ; then
160+
has_lang=1
161+
fi
162+
done
163+
if [ ! "x$has_lang" = "x1" ] ; then
164+
echo "ERROR: Provided language code \`$newlang' is not in the list of glibc's language codes." >&2
165+
echo "You can run \`locale -a | sed 's|\\..*||g' | sort | uniq\` to get the language code list." >&2
166+
exit 1
167+
fi
168+
echo "-- Creating untranslated PO file for $newlang ..."
169+
msginit -i "$po_dir"/"$GETTEXT_DOMAIN.pot" -o "$po_dir"/"$newlang".po \
170+
--language "${newlang%%.*}.UTF-8"
171+
msgfmt --statistics -o /dev/null "$po_dir"/"$newlang".po
172+
echo "-- Succeffully initialized PO file at $po_dir/$newlang.po."
173+
}
174+
175+
generate_pot() {
176+
local _cursrc_lang _var _srcname
177+
if [ -f "$po_dir"/"$GETTEXT_DOMAIN".pot ] ; then
178+
echo "ERROR: POT file $po_dir/$GETTEXT_DOMAIN.pot already exists." >&2
179+
echo "You may want to update that POT file instead of creating a new one." >&2
180+
exit 1
181+
fi
182+
echo "-- Creating PO template for maintscripts of $PKGNAME ..."
183+
pushd "$ab_dir"
184+
# If it contains several sources, generate them one by one,
185+
# then concatenate them together using msgcat.
186+
for src in "${GETTEXT_SRCS[@]}" ; do
187+
_var="$(transform_varname "${src##*/}")"
188+
_var="GETTEXT_SRCLANG_$src"
189+
# Using namerefs to avoid invalid indirection.
190+
declare -n _cursrc_lang="$_var"
191+
if [ "x$_cursrc_lang" = "x" ] ; then
192+
_cursrc_lang="Shell"
193+
fi
194+
touch "$po_dir"/"$GETTEXT_DOMAIN".pot
195+
xgettext -o "$po_dir"/"$GETTEXT_DOMAIN".pot \
196+
"$src" \
197+
--language="$_cursrc_lang" \
198+
--copyright-holder="AOSC OS Maintainers <maintainers@aosc.io>" \
199+
--package-name="$GETTEXT_DOMAIN" \
200+
--package-version="0.1.0" \
201+
--join-existing
202+
done
203+
popd
204+
echo "-- Successfully created PO template file at $po_dir/$GETTEXT_DOMAIN.pot."
205+
}
206+
207+
update_po() {
208+
echo "-- Updating PO template ..."
209+
pushd "$ab_dir"
210+
for src in "${GETTEXT_SRCS[@]}" ; do
211+
_var="$(transform_varname "${src##*/}")"
212+
_var="GETTEXT_SRCLANG_$src"
213+
# Using namerefs to avoid invalid indirection.
214+
declare -n _cursrc_lang="$_var"
215+
if [ "x$_cursrc_lang" = "x" ] ; then
216+
_cursrc_lang="Shell"
217+
fi
218+
xgettext -o "$po_dir"/"$GETTEXT_DOMAIN".pot \
219+
"$src" \
220+
--language="$_cursrc_lang" \
221+
--copyright-holder="AOSC OS Maintainers <maintainers@aosc.io>" \
222+
--package-name="$GETTEXT_DOMAIN" \
223+
--package-version="0.1.0" \
224+
--join-existing
225+
done
226+
popd
227+
echo "-- Updating PO catalogs ..."
228+
for lang in "${GETTEXT_LINUGAS[@]}" ; do
229+
# NOTE: msgupdate -U def.po ref.pot
230+
# NOTE: make sure po files are newer than the POT file
231+
msgupdate -U "$po_dir"/"$lang".po \
232+
"$po_dir"/"$GETTEXT_DOMAIN".pot
233+
touch "$po_dir"/"$lang".po
234+
done
235+
if ! check_po ; then
236+
echo "ERROR: Error(s) detected in the updated translations." >&2
237+
echo "You must fix them before committing." >&2
238+
return 1
239+
fi
240+
echo "-- Hooray! Successfully updated the PO files, no new strings to translate for now."
241+
return 0
242+
}
243+
244+
check_po() {
245+
local bad_langs=()
246+
echo "-- Checking translated catalogs ..."
247+
for lang in "${GETTEXT_LINGUAS[@]}" ; do
248+
echo "-- Checking $lang ..."
249+
# Output the stats of the updated PO file.
250+
# NOTE: This program does not fail if fuzzy messages are present in
251+
# the source file.
252+
msgfmt --statistics -o /dev/null "$po_dir"/"$lang".po
253+
if ! msgcmp -N "$po_dir"/"$lang".po "$po_dir"/"$GETTEXT_DOMAIN".pot ; then
254+
echo "ERROR: Fuzzy strings detected. Please translate them before committing." >&2
255+
bad_langs+=("$lang")
256+
else
257+
echo "-- No errors found in $lang.po"
258+
fi
259+
done
260+
if [ "${#bad_langs[@]}" -gt 0 ] ; then
261+
echo "ERROR: Error(s) detected. Please check the output for details." >&2
262+
return 1
263+
fi
264+
echo "-- Check complete, ${#GETTEXT_LINGUAS[@]} languages checked, no errors found."
265+
return 0
266+
}
267+
268+
case "${action,,}" in
269+
g|ge|gen|generate)
270+
action="generate"
271+
try_read_defines "$1"
272+
generate_pot
273+
;;
274+
cr|cre|crea|create)
275+
action="create"
276+
new_lang="$1"
277+
shift
278+
try_read_defines "$1"
279+
preq_check
280+
create_lang "$new_lang"
281+
;;
282+
u|upd|update)
283+
action="update"
284+
try_read_defines "$1"
285+
preq_check
286+
update_po
287+
;;
288+
c|ch|chk|check)
289+
action="check"
290+
try_read_defines "$1"
291+
preq_check
292+
check_po
293+
;;
294+
*)
295+
echo "ERROR: Invalid action $action." >&2
296+
usage
297+
exit 1
298+
esac

0 commit comments

Comments
 (0)