diff --git a/data/skeletons/d.m4 b/data/skeletons/d.m4
index 331c06a8..5320c483 100644
--- a/data/skeletons/d.m4
+++ b/data/skeletons/d.m4
@@ -192,7 +192,12 @@ b4_symbol_foreach([b4_token_enum])dnl
}
])
-
+# b4_symbol_translate(STRING)
+# ---------------------------
+# Used by "bison" in the array of symbol names to mark those that
+# require translation.
+m4_define([b4_symbol_translate],
+[[_($1)]])
## -------------- ##
## Symbol kinds. ##
@@ -252,8 +257,17 @@ m4_define([b4_declare_symbol_enum],
final void toString(W)(W sink) const
if (isOutputRange!(W, char))
{
- string yystr = yytname_[yycode_];
+ immutable string[] yy_sname = @{
+ ]b4_symbol_names[
+ @};]b4_has_translations_if([[
+ /* YYTRANSLATABLE[SYMBOL-NUM] -- Whether YY_SNAME[SYMBOL-NUM] is
+ internationalizable. */
+ immutable ]b4_int_type_for([b4_translatable])[[] yytranslatable = @{
+ ]b4_translatable[
+ @};
+ put(sink, yy_sname[yycode_]);]], [[
+ string yystr = yytname_[yycode_];
if (yystr[0] == '"')
{
strip_quotes:
@@ -280,9 +294,7 @@ m4_define([b4_declare_symbol_enum],
{
put(sink, "end of input");
return;
- }
-
- put(sink, yystr);
+ }]])[
}
}
]])
diff --git a/data/skeletons/lalr1.d b/data/skeletons/lalr1.d
index fd0038d6..ede7d8c7 100644
--- a/data/skeletons/lalr1.d
+++ b/data/skeletons/lalr1.d
@@ -40,6 +40,29 @@ version(D_Version2) {
]b4_user_post_prologue[
]b4_percent_code_get([[imports]])[
import std.format;
+import std.conv;
+
+/**
+ * Handle error message internationalisation.
+ */
+static if (!is(typeof(YY_))) {
+ version(YYENABLE_NLS)
+ {
+ version(ENABLE_NLS)
+ {
+ extern(C) char* dgettext(const char*, const char*);
+ string YY_(const char* s)
+ {
+ return to!string(dgettext("bison-runtime", s));
+ }
+ }
+ }
+ static if (!is(typeof(YY_)))
+ {
+ pragma(inline, true)
+ string YY_(string msg) { return msg; }
+ }
+}
/**
* A Bison parser, automatically generated from ]m4_bpatsubst(b4_file_name, [^"\(.*\)"$], [\1])[.
@@ -680,20 +703,38 @@ m4_popdef([b4_at_dollar])])dnl
immutable int argmax = 5;
SymbolKind[] yyarg = new SymbolKind[argmax];
int yycount = yysyntaxErrorArguments(yyctx, yyarg, argmax);
- string res = "syntax error, unexpected ";
- res ~= format!"%s"(yyarg[0]);
- if (yycount < argmax + 1)
+ string res, yyformat;
+ import std.string;
+ switch (yycount)
{
- for (int yyi = 1; yyi < yycount; yyi++)
- {
- res ~= yyi == 1 ? ", expecting " : " or ";
- res ~= format!"%s"(SymbolKind(yyarg[yyi]));
- }
+ case 1:
+ yyformat = YY_("syntax error, unexpected %s");
+ res = format(yyformat, yyarg[0]);
+ break;
+ case 2:
+ yyformat = YY_("syntax error, unexpected %s, expecting %s");
+ res = format(yyformat, yyarg[0], yyarg[1]);
+ break;
+ case 3:
+ yyformat = YY_("syntax error, unexpected %s, expecting %s or %s");
+ res = format(yyformat, yyarg[0], yyarg[1], yyarg[2]);
+ break;
+ case 4:
+ yyformat = YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+ res = format(yyformat, yyarg[0], yyarg[1], yyarg[2], yyarg[3]);
+ break;
+ case 5:
+ yyformat = YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+ res = format(yyformat, yyarg[0], yyarg[1], yyarg[2], yyarg[3], yyarg[4]);
+ break;
+ default:
+ res = YY_("syntax error");
+ break;
}
yyerror(]b4_locations_if([yyctx.getLocation(), ])[res);
}]],
[[simple]], [[
- yyerror(]b4_locations_if([yyctx.getLocation(), ])["syntax error");]])[
+ yyerror(]b4_locations_if([yyctx.getLocation(), ])[YY_("syntax error"));]])[
}
]b4_parse_error_bmatch(
diff --git a/doc/bison.texi b/doc/bison.texi
index 0a55f343..c749079f 100644
--- a/doc/bison.texi
+++ b/doc/bison.texi
@@ -13953,6 +13953,66 @@ or nonzero, full tracing.
Identify the Bison version and skeleton used to generate this parser.
@end deftypecv
+The internationalization in D is very simmilar to the one in C. The D
+parser uses @code{dgettext} for translating Bison messages.
+
+To enable internationalisation, compile using
+@code{-version ENABLE_NLS -version YYENABLE_NLS} and import
+@code{bindtextdomain} and @code{textdomain} from C:
+
+@example
+extern(C) char* bindtextdomain(const char* domainname, const char* dirname);
+extern(C) char* textdomain(const char* domainname);
+@end example
+
+The main function should load the translation catalogues, similarly to the
+@file{c/bistromathic} example:
+
+@example
+int main()
+@{
+ import core.stdc.locale;
+
+ // Set up internationalization.
+ setlocale(LC_ALL, "");
+ // Use Bison's standard translation catalogue for error messages
+ // (the generated messages).
+ bindtextdomain("bison-runtime", BISON_LOCALEDIR);
+ // For the translation catalogue of your own project, use the
+ // name of your project.
+ bindtextdomain("bison", LOCALEDIR);
+ textdomain("bison");
+
+ // usual main content
+ ...
+@}
+@end example
+
+For user messages translations, the user must implement the
+@code{string} _(@code{const char*} @var{msg}) function and it is recommended
+to use @code{gettext}:
+
+@example
+%code imports @{
+ static if (!is(typeof(_)))
+ @{
+ version(ENABLE_NLS)
+ @{
+ extern(C) char* gettext(const char*);
+ string _(const char* s)
+ @{
+ return to!string(gettext(s));
+ @}
+ @}
+ @}
+ static if (!is(typeof(_)))
+ @{
+ pragma(inline, true)
+ string _(string msg) @{ return msg; @}
+ @}
+@}
+@end example
+
@node D Parser Context Interface
@subsection D Parser Context Interface
The parser context provides information to build error reports when you
diff --git a/tests/calc.at b/tests/calc.at
index b1428316..3d319645 100644
--- a/tests/calc.at
+++ b/tests/calc.at
@@ -654,6 +654,23 @@ m4_define([_AT_DATA_CALC_Y(d)],
};
%printer { fprintf (yyo, "%d", $$); } ;
+%code {
+]AT_TOKEN_TRANSLATE_IF([[
+ static string _(string s)
+ {
+ switch (s)
+ {
+ case "end of input":
+ return "end of file";
+ case "number":
+ return "nombre";
+ default:
+ return s;
+ }
+ }
+]])[
+}
+
/* Bison Declarations */
%token EOF 0 ]AT_TOKEN_TRANSLATE_IF([_("end of file")], ["end of input"])[
%token NUM "number"
@@ -665,7 +682,7 @@ m4_define([_AT_DATA_CALC_Y(d)],
STAR "*"
SLASH "/"
POW "^"
- EOL "\n"
+ EOL "'\\n'"
LPAR "("
RPAR ")"
NOT "!"