Add the TRANSLATE GENERATE command to generate a skeleton .rem file for localization.

This commit is contained in:
Dianne Skoll
2025-01-14 13:27:04 -05:00
parent a35920f28e
commit 96c11e89eb
9 changed files with 348 additions and 9 deletions

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ tests/test.out
www/Makefile
gmon.out
tests/once.timestamp
src/xlat.c

View File

@@ -478,11 +478,13 @@ The \fB\-\-version\fR option causes \fBRemind\fR to print its version number
to standard output and then exit.
.TP
.B \-\-print-errs
The \fB\-\-print-errs\fR option causes \fBRemind\fR to print all possible
error messages to standard output and then exit. The messages are printed
in a format suitable for the first argument of a TRANSLATE command. If
you TRANSLATE the error messages, then \fBRemind\fR will use the translated
versions when outputting error and warning messages.
The \fB\-\-print-errs\fR option causes \fBRemind\fR to print all
possible error messages to standard output and then exit. The
messages are printed in a format suitable for the first argument of a
TRANSLATE command. If you TRANSLATE the error messages, then
\fBRemind\fR will use the translated versions when outputting error
and warning messages. See also TRANSLATE GENERATE in the section
"THE TRANSLATION TABLE".
.RS
.PP
Note that if an untranslated message contains printf-style formatting
@@ -5754,13 +5756,40 @@ function, as follows:
By using \fBTRANSLATE\fR and \fB_\fR judiciously, you can make your
reminder files easy to translate.
.PP
\fBTRANSLATE\fR has three additional forms: If it is followed
\fBTRANSLATE\fR has four additional forms: If it is followed
by \fIone\fR quoted string instead of two, then \fBRemind\fR deletes the
translation table entry for that string. If it is followed by
the keyword \fBDUMP\fR, then \fBRemind\fR dumps all translation table entries
to standard output. And if it is followed by \fBCLEAR\fR, then
\fBRemind\fR deletes all of the translation table entries.
.PP
The fourth form, \fBTRANSLATE GENERATE\fR, dumps all of the
strings that can be localized (as a series of TRANSLATE commands) to
standard output. Strings that are already localized are output
with their localization; strings that are not localized are
output as:
.PP
.nf
TRANSLATE "untranslated" ""
.nf
.PP
If you want to add a new language, you can obtain a skeleton translation
file by running:
.PP
.nf
echo "TRANSLATE GENERATE" | remind -h - > /tmp/skeleton.rem
.fi
.PP
If you have an existing language file that is missing some translations,
you can update it by running:
.PP
.nf
(echo INCLUDE mylang.rem; echo TRANSLATE GENERATE) | \\
remind -h - > /tmp/mylang-update.rem
.fi
.PP
and then editing \fBmylang-update.rem\fR to add in the missing translations.
.PP
Note that if you \fBSET\fR various translation-related system
variables such as \fB$Monday\fR, \fB$December\fR, \fB$Ago\fR, etc,
then \fBRemind\fR \fIalso\fR makes a corresponding translation

View File

@@ -31,9 +31,11 @@ REMINDSRCS= calendar.c dedupe.c dynbuf.c dorem.c dosubst.c expr.c \
hbcal.c init.c main.c md5.c moon.c omit.c queue.c \
sort.c token.c trans.c trigger.c userfns.c utils.c var.c
XLATSRC= xlat.c
REMINDHDRS=config.h custom.h dynbuf.h err.h globals.h hashtab.h \
md5.h protos.h rem2ps.h types.h version.h
REMINDOBJS= $(REMINDSRCS:.c=.o)
REMINDOBJS= $(REMINDSRCS:.c=.o) $(XLATSRC:.c=.o)
all: remind rem2ps
@@ -43,6 +45,13 @@ test: all
.c.o:
@CC@ -c @CPPFLAGS@ @CFLAGS@ @DEFS@ $(CEXTRA) -DSYSDIR=$(datarootdir)/remind -I. -I$(srcdir) $<
xlat.c: $(REMINDSRCS)
-echo "#include <stddef.h>" > xlat.c
-echo "char const *translatables[] = {" >> xlat.c
-cat $(REMINDSRCS) | grep 'tr(".*")' *.c | sed -e 's/.*tr."/"/' -e 's/").*/"/' | sort | uniq | sed -e 's/^/ /' -e 's/$$/,/' >> xlat.c
-echo " NULL" >> xlat.c
-echo "};" >> xlat.c
$(REMINDOBJS): $(REMINDHDRS)
rem2ps: rem2ps.o dynbuf.o json.o
@@ -80,7 +89,7 @@ install-stripped: install
strip $(DESTDIR)$(bindir)/rem2ps || true
clean:
rm -f *.o *~ core *.bak $(PROGS)
rm -f *.o *~ core *.bak $(PROGS) $(XLATSRC)
clobber:
rm -f *.o *~ remind rem2ps test.out core *.bak

View File

@@ -237,3 +237,6 @@ EXTERN int SuppressLRM
= 0
#endif
;
/* Translatable messages */
extern char const *translatables[];

View File

@@ -273,3 +273,5 @@ int GetTranslatedStringTryingVariants(char const *orig, DynamicBuffer *out);
char const *GetErr(int r);
char const *tr(char const *s);
void print_escaped_string(FILE *fp, char const *s);
void GenerateSysvarTranslationTemplates(void);
void TranslationTemplate(char const *msg);

View File

@@ -34,6 +34,65 @@ typedef struct xlat {
hash_table TranslationTable;
static XlateItem *FindTranslation(char const *orig);
static void print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind);
void
TranslationTemplate(char const *in)
{
if (!*in) {
return;
}
if (!strcmp(in, "LANGID")) {
return;
}
printf("TRANSLATE ");
print_escaped_string_helper(stdout, in, 1);
if (FindTranslation(in)) {
printf(" ");
print_escaped_string_helper(stdout, tr(in), 1);
printf("\n");
} else {
printf(" \"\"\n");
}
}
static void
GenerateTranslationTemplate(void)
{
int i;
printf("# Translation table template\n\n");
printf("TRANSLATE \"LANGID\" ");
print_escaped_string_helper(stdout, tr("LANGID"), 1);
printf("\n\n");
/* Weekday Names */
for (i=0; i<7; i++) {
TranslationTemplate(DayName[i]);
}
/* Month Names */
for (i=0; i<12; i++) {
TranslationTemplate(MonthName[i]);
}
/* Translatable system variables */
GenerateSysvarTranslationTemplates();
/* Error messages */
for (i=0; i<NumErrs; i++) {
TranslationTemplate(ErrMsg[i]);
}
/* Other translatables */
for (i=0; translatables[i] != NULL; i++) {
TranslationTemplate(translatables[i]);
}
}
/***************************************************************/
/* */
/* AllocateXlateItem - Allocate a new translation item */
@@ -113,6 +172,11 @@ ClearTranslationTable(void)
void
print_escaped_string(FILE *fp, char const *s)
{
print_escaped_string_helper(fp, s, 0);
}
static void
print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind) {
putc('"', fp);
while(*s) {
switch(*s) {
@@ -126,7 +190,11 @@ print_escaped_string(FILE *fp, char const *s)
case '"': putc('\\', fp); putc('"', fp); break;
case '\\': putc('\\', fp); putc('\\', fp); break;
default:
putc(*s, fp); break;
if (esc_for_remind && *s == '[') {
fprintf(fp, "[\"[\"]");
} else {
putc(*s, fp); break;
}
}
s++;
}
@@ -353,6 +421,12 @@ DoTranslate(ParsePtr p)
ClearTranslationTable();
return OK;
}
if (!StrCmpi(DBufValue(&orig), "generate")) {
DBufFree(&orig);
if (r) return r;
GenerateTranslationTemplate();
return OK;
}
return E_PARSE_ERR;
}

View File

@@ -1239,6 +1239,29 @@ set_components_from_lat_and_long(void)
}
}
void GenerateSysvarTranslationTemplates(void)
{
int i;
int j;
char const *msg;
for (i=0; i< (int) NUMSYSVARS; i++) {
if (SysVarArr[i].type == TRANS_TYPE) {
msg = SysVarArr[i].value;
/* We've already done month and day names */
for (j=0; j<7; j++) {
if (!strcmp(msg, DayName[j])) {
return;
}
}
for (j=0; j<12; j++) {
if (!strcmp(msg, MonthName[j])) {
return;
}
}
TranslationTemplate(msg);
}
}
}
void
print_sysvar_tokens(void)
{

View File

@@ -641,6 +641,11 @@ set a 9 *
set a 9 * ]
EOF
# Translation template generateion
../src/remind -h - 1 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
TRANSLATE GENERATE
EOF
# Languages
for i in ../include/lang/??.rem ; do
../src/remind -r -q "-ii=\"$i\"" ../tests/tstlang.rem 1 Feb 2024 13:34 >> ../tests/test.out 2>&1

View File

@@ -24332,6 +24332,199 @@ set a 9 * ]
Reminders for Thursday, 1st February, 2024:
No reminders.
# Translation table template
TRANSLATE "LANGID" "en"
TRANSLATE "Monday" ""
TRANSLATE "Tuesday" ""
TRANSLATE "Wednesday" ""
TRANSLATE "Thursday" ""
TRANSLATE "Friday" ""
TRANSLATE "Saturday" ""
TRANSLATE "Sunday" ""
TRANSLATE "January" ""
TRANSLATE "February" ""
TRANSLATE "March" ""
TRANSLATE "April" ""
TRANSLATE "May" ""
TRANSLATE "June" ""
TRANSLATE "July" ""
TRANSLATE "August" ""
TRANSLATE "September" ""
TRANSLATE "October" ""
TRANSLATE "November" ""
TRANSLATE "December" ""
TRANSLATE "ago" ""
TRANSLATE "am" ""
TRANSLATE "and" ""
TRANSLATE "Ok" ""
TRANSLATE "Missing ']'" ""
TRANSLATE "Missing quote" ""
TRANSLATE "Expression too complex" ""
TRANSLATE "Missing ')'" ""
TRANSLATE "Undefined function" ""
TRANSLATE "Illegal character" ""
TRANSLATE "Expecting binary operator" ""
TRANSLATE "Out of memory" ""
TRANSLATE "Ill-formed number" ""
TRANSLATE "Can't coerce" ""
TRANSLATE "Type mismatch" ""
TRANSLATE "Date overflow" ""
TRANSLATE "Division by zero" ""
TRANSLATE "Undefined variable" ""
TRANSLATE "Unexpected end of line" ""
TRANSLATE "Unexpected end of file" ""
TRANSLATE "I/O error" ""
TRANSLATE "Internal error" ""
TRANSLATE "Bad date specification" ""
TRANSLATE "Not enough arguments" ""
TRANSLATE "Too many arguments" ""
TRANSLATE "Ill-formed time" ""
TRANSLATE "Number too high" ""
TRANSLATE "Number too low" ""
TRANSLATE "Can't open file" ""
TRANSLATE "INCLUDE nested too deeply (max. 9)" ""
TRANSLATE "Parse error" ""
TRANSLATE "Can't compute trigger" ""
TRANSLATE "Too many nested IFs" ""
TRANSLATE "ELSE with no matching IF" ""
TRANSLATE "ENDIF with no matching IF" ""
TRANSLATE "Can't OMIT every weekday" ""
TRANSLATE "Extraneous token(s) on line" ""
TRANSLATE "POP-OMIT-CONTEXT without matching PUSH-OMIT-CONTEXT" ""
TRANSLATE "RUN disabled" ""
TRANSLATE "Domain error" ""
TRANSLATE "Invalid identifier" ""
TRANSLATE "Too many recursive function calls" ""
TRANSLATE "Cannot modify system variable" ""
TRANSLATE "C library function can't represent date/time" ""
TRANSLATE "Attempt to redefine built-in function" ""
TRANSLATE "Can't nest function definition in expression" ""
TRANSLATE "Must fully specify date to use repeat factor" ""
TRANSLATE "Year specified twice" ""
TRANSLATE "Month specified twice" ""
TRANSLATE "Day specified twice" ""
TRANSLATE "Unknown token" ""
TRANSLATE "Must specify month in OMIT command" ""
TRANSLATE "Too many full OMITs (max. 1000)" ""
TRANSLATE "Warning: PUSH-OMIT-CONTEXT without matching POP-OMIT-CONTEXT" ""
TRANSLATE "Error reading" ""
TRANSLATE "Expecting end-of-line" ""
TRANSLATE "Invalid Hebrew date" ""
TRANSLATE "iif(): odd number of arguments required" ""
TRANSLATE "Warning: Missing ENDIF" ""
TRANSLATE "Expecting comma" ""
TRANSLATE "Weekday specified twice" ""
TRANSLATE "Only use one of BEFORE, AFTER or SKIP" ""
TRANSLATE "Can't nest MSG, MSF, RUN, etc. in expression" ""
TRANSLATE "Repeat value specified twice" ""
TRANSLATE "Delta value specified twice" ""
TRANSLATE "Back value specified twice" ""
TRANSLATE "ONCE keyword used twice. (Hah.)" ""
TRANSLATE "Expecting time after AT" ""
TRANSLATE "THROUGH/UNTIL keyword used twice" ""
TRANSLATE "Incomplete date specification" ""
TRANSLATE "FROM/SCANFROM keyword used twice" ""
TRANSLATE "Variable" ""
TRANSLATE "Value" ""
TRANSLATE "*UNDEFINED*" ""
TRANSLATE "Entering UserFN" ""
TRANSLATE "Leaving UserFN" ""
TRANSLATE "Expired" ""
TRANSLATE "fork() failed - can't do queued reminders" ""
TRANSLATE "Can't access file" ""
TRANSLATE "Illegal system date: Year is less than %d\n" ""
TRANSLATE "Unknown debug flag '%c'\n" ""
TRANSLATE "Unknown option '%c'\n" ""
TRANSLATE "Unknown user '%s'\n" ""
TRANSLATE "Could not change gid to %d\n" ""
TRANSLATE "Could not change uid to %d\n" ""
TRANSLATE "Out of memory for environment\n" ""
TRANSLATE "Missing '=' sign" ""
TRANSLATE "Missing variable name" ""
TRANSLATE "Missing expression" ""
TRANSLATE "Remind: '-i' option: %s\n" ""
TRANSLATE "No reminders." ""
TRANSLATE "%d reminder(s) queued for later today.\n" ""
TRANSLATE "Expecting number" ""
TRANSLATE "Undefined WARN function" ""
TRANSLATE "Can't convert between time zones" ""
TRANSLATE "No files matching *.rem" ""
TRANSLATE "String too long" ""
TRANSLATE "Time specified twice" ""
TRANSLATE "Cannot specify DURATION without specifying AT" ""
TRANSLATE "Expecting weekday name" ""
TRANSLATE "Duplicate argument name" ""
TRANSLATE "Expression evaluation is disabled" ""
TRANSLATE "Time limit for expression evaluation exceeded" ""
TRANSLATE " %s(%d): ["["]#%d] %s function `%s'" ""
TRANSLATE "%s function `%s' defined at %s:%d does not use its argument" ""
TRANSLATE "%s function `%s' defined at %s:%d should take 1 argument but actually takes %d" ""
TRANSLATE "%s is deprecated; use %s instead" ""
TRANSLATE "%s(%d): IF without ENDIF\n" ""
TRANSLATE "(Security note: $RunOff variable tested.)" ""
TRANSLATE "Accepting \"%s\" for $Latitude/$Longitude, but you should use the \"C\" locale decimal separator \".\" instead" ""
TRANSLATE "Caching directory `%s' listing\n" ""
TRANSLATE "Caching file `%s' in memory\n" ""
TRANSLATE "Called from" ""
TRANSLATE "Cannot open `%s' for writing: %s\n" ""
TRANSLATE "Cannot stat %s - not running as daemon!\n" ""
TRANSLATE "Cannot use AT clause in multitrig() function" ""
TRANSLATE "Do not use ["["]] around expression in SET command" ""
TRANSLATE "Error: THROUGH date earlier than start date" ""
TRANSLATE "Executing `%s' for INCLUDECMD and caching as `%s'\n" ""
TRANSLATE "Found cached directory listing for `%s'\n" ""
TRANSLATE "Function `%s' defined at %s:%d should take %d argument%s, but actually takes %d" ""
TRANSLATE "Function `%s' redefined (previously defined at %s:%d)" ""
TRANSLATE "GetValidHebDate: Bad adarbehave value %d" ""
TRANSLATE "In" ""
TRANSLATE "Missing REM type; assuming MSG" ""
TRANSLATE "No Adar A in %d" ""
TRANSLATE "No substition function `%s' defined" ""
TRANSLATE "Not setting $OnceFile: Already processed a reminder with a ONCE clause" ""
TRANSLATE "OMIT: UNTIL not allowed; did you mean THROUGH?" ""
TRANSLATE "POP-OMIT-CONTEXT at %s:%d matches PUSH-OMIT-CONTEXT in different file: %s:%d" ""
TRANSLATE "Reading `%s': Found in cache\n" ""
TRANSLATE "Reading `%s': Opening file on disk\n" ""
TRANSLATE "Reading `-': Reading stdin\n" ""
TRANSLATE "Reading command `%s': Found in cache\n" ""
TRANSLATE "SATISFY: constant 0 will never be true" ""
TRANSLATE "SATISFY: constant \"\" will never be true" ""
TRANSLATE "SATISFY: expression has no reference to trigdate() or $T..." ""
TRANSLATE "SECURITY: Won't read non-root-owned file or directory when running as root!\n" ""
TRANSLATE "SECURITY: Won't read world-writable file or directory!\n" ""
TRANSLATE "Scanning directory `%s' for *.rem files\n" ""
TRANSLATE "Undefined %s function: `%s'" ""
TRANSLATE "Unmatched PUSH-OMIT-CONTEXT at %s(%d)" ""
TRANSLATE "Unrecognized command; interpreting as REM" ""
TRANSLATE "Warning: Function name `%s...' truncated to `%s'" ""
TRANSLATE "Warning: OMIT is ignored if you use OMITFUNC" ""
TRANSLATE "Warning: SCANFROM is ignored in two-argument form of evaltrig()" ""
TRANSLATE "Warning: UNTIL/THROUGH date earlier than FROM date" ""
TRANSLATE "Warning: UNTIL/THROUGH date earlier than SCANFROM date" ""
TRANSLATE "Warning: UNTIL/THROUGH date earlier than start date" ""
TRANSLATE "Warning: Unable to save ONCE timestamp to %s: %s" ""
TRANSLATE "Warning: Unterminated %%(...) substitution sequence" ""
TRANSLATE "Warning: Unterminated %%{...} substitution sequence" ""
TRANSLATE "Warning: Useless use of UNTIL with fully-specified date and no *rep" ""
TRANSLATE "Warning: Variable name `%.*s...' truncated to `%.*s'" ""
TRANSLATE "You have OMITted everything! The space-time continuum is at risk." ""
TRANSLATE "am" ""
TRANSLATE "at" ""
TRANSLATE "did you mean" ""
TRANSLATE "from now" ""
TRANSLATE "here" ""
TRANSLATE "hour" ""
TRANSLATE "minute" ""
TRANSLATE "now" ""
TRANSLATE "on" ""
TRANSLATE "pm" ""
TRANSLATE "psmoon() is deprecated; use SPECIAL MOON instead." ""
TRANSLATE "psshade() is deprecated; use SPECIAL SHADE instead." ""
TRANSLATE "remaining call frames omitted" ""
TRANSLATE "today" ""
TRANSLATE "was" ""
Agenda pel dijous, 1 de febrer de 2024:
Language: ca