diff --git a/build/cmake/tests/base/CMakeLists.txt b/build/cmake/tests/base/CMakeLists.txt index 71ca50e243..9fb576c1d6 100644 --- a/build/cmake/tests/base/CMakeLists.txt +++ b/build/cmake/tests/base/CMakeLists.txt @@ -106,6 +106,8 @@ if(wxUSE_XML) endif() set(TEST_DATA + intl/en_GB/internat.mo + intl/en_GB/internat.po intl/fr/internat.mo intl/fr/internat.po intl/ja/internat.mo diff --git a/docs/doxygen/overviews/envvars.h b/docs/doxygen/overviews/envvars.h index 7b6386d8e4..21079cc25a 100644 --- a/docs/doxygen/overviews/envvars.h +++ b/docs/doxygen/overviews/envvars.h @@ -51,6 +51,12 @@ wxWidgets programs. default value if it's not a number, so that e.g. setting it to "yes" suppresses all GTK diagnostics while setting it to 16 only suppresses GTK warning messages.} +@itemdef{WXLANGUAGE, + This variable can be set to override OS setting of preferred languages + and make wxUILocale::GetPreferredUILanguages() return the set list + instead. The format is same as GNU's LANGUAGE + variable: a colon-separated list of language codes.} */ @see wxSystemOptions diff --git a/include/wx/translation.h b/include/wx/translation.h index ca97649a4b..aaa55571d4 100644 --- a/include/wx/translation.h +++ b/include/wx/translation.h @@ -160,7 +160,7 @@ public: // add catalog for the given domain returning true if it could be found by // wxTranslationsLoader - bool AddAvailableCatalog(const wxString& domain); + bool AddAvailableCatalog(const wxString& domain, wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US); // add standard wxWidgets catalog ("wxstd") bool AddStdCatalog(); @@ -193,6 +193,15 @@ public: static const wxString& GetUntranslatedString(const wxString& str); private: + enum class Translations + { + NotNeeded = -1, + NotFound = 0, + Found = 1 + }; + + Translations DoAddCatalog(const wxString& domain, wxLanguage msgIdLanguage); + // perform loading of the catalog via m_loader bool LoadCatalog(const wxString& domain, const wxString& lang); @@ -203,6 +212,8 @@ private: static void SetNonOwned(wxTranslations *t); friend class wxLocale; + wxString DoGetBestAvailableTranslation(const wxString& domain, const wxString& additionalAvailableLanguage); + private: wxString m_lang; wxTranslationsLoader *m_loader; diff --git a/interface/wx/translation.h b/interface/wx/translation.h index 401a421bdd..d90a896d0e 100644 --- a/interface/wx/translation.h +++ b/interface/wx/translation.h @@ -85,6 +85,11 @@ public: translations offered to the user. To do this, pass the app's main catalog as @a domain. + @note + The returned list does not include messages ID language, i.e. the + language (typically English) included in the source code. In the use + case described above, that language needs to be added manually. + @see GetBestTranslation() */ wxArrayString GetAvailableTranslations(const wxString& domain) const; @@ -97,6 +102,12 @@ public: it simply returns the language set with SetLanguage() if it's available or empty string otherwise. + @warning + This function does not consider messages ID language (typically + English) and can return inappropriate language if it is anywhere in + user's preferred languages list. Use GetBestTranslation() instead + unless you have very specific needs. + @since 3.3.0 */ wxString GetBestAvailableTranslation(const wxString& domain); @@ -158,6 +169,16 @@ public: All loaded catalogs will be used for message lookup by GetString() for the current locale. + @param domain + The catalog domain to add. + + @param msgIdLanguage + Specifies the language of "msgid" strings in source code + (i.e. arguments to GetString(), wxGetTranslation() and the _() macro). + It is used if AddCatalog() cannot find any catalog for current language: + if the language is same as source code language, then strings from source + code are used instead. + @return @true if catalog was successfully loaded, @false otherwise, usually because it wasn't found. Note that unlike AddCatalog() this @@ -167,9 +188,10 @@ public: selected or system-default languages, but is not necessarily an error if no translations are needed in the first place. - @since 3.3.0 + @since 3.2.5 */ - bool AddAvailableCatalog(const wxString& domain); + bool AddAvailableCatalog(const wxString& domain, + wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US); /** Add a catalog for use with the current locale or fall back to the diff --git a/src/common/translation.cpp b/src/common/translation.cpp index 0d58caf3a5..152d1786ef 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -116,14 +116,6 @@ void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr) #endif // wxUSE_LOG_TRACE/!wxUSE_LOG_TRACE -// Use locale-based detection as a fallback -wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available)) -{ - const wxString lang = wxUILocale::GetLanguageCanonicalName(wxUILocale::GetSystemLocale()); - wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang); - return lang; -} - wxString GetPreferredUILanguage(const wxArrayString& available) { wxVector preferred = wxUILocale::GetPreferredUILanguages(); @@ -168,7 +160,7 @@ wxString GetPreferredUILanguage(const wxArrayString& available) if (!langNoMatchRegion.empty()) return langNoMatchRegion; - return GetPreferredUILanguageFallback(available); + return wxString(); } } // anonymous namespace @@ -1316,65 +1308,50 @@ bool wxTranslations::AddStdCatalog() // the name without the version if it's not found, as message catalogs // typically won't have the version in their names under non-Unix platforms // (i.e. where they're not installed by our own "make install"). - if ( AddAvailableCatalog("wxstd-" wxSTRINGIZE(wxMAJOR_VERSION) "." wxSTRINGIZE(wxMINOR_VERSION)) ) - return true; + wxString domain("wxstd-" wxSTRINGIZE(wxMAJOR_VERSION) "." wxSTRINGIZE(wxMINOR_VERSION)); + if ( GetBestAvailableTranslation(domain).empty() ) + domain = wxS("wxstd"); - if ( AddCatalog(wxS("wxstd")) ) - return true; - - return false; + return AddCatalog(domain); } -bool wxTranslations::AddAvailableCatalog(const wxString& domain) +bool wxTranslations::AddAvailableCatalog(const wxString& domain, wxLanguage msgIdLanguage) { - const wxString domain_lang = GetBestAvailableTranslation(domain); + return DoAddCatalog(domain, msgIdLanguage) == Translations::Found; +} + +bool wxTranslations::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) +{ + return DoAddCatalog(domain, msgIdLanguage) != Translations::NotFound; +} + +wxTranslations::Translations wxTranslations::DoAddCatalog(const wxString& domain, + wxLanguage msgIdLanguage) +{ + const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage); + const wxString domain_lang = GetBestTranslation(domain, msgIdLang); if ( domain_lang.empty() ) { wxLogTrace(TRACE_I18N, wxS("no suitable translation for domain '%s' found"), domain); - return false; + return Translations::NotFound; } - return LoadCatalog(domain, domain_lang); -} - -bool wxTranslations::AddCatalog(const wxString& domain, - wxLanguage msgIdLanguage) -{ - if ( AddAvailableCatalog(domain) ) - return true; - - const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage); - - // Check if the original strings can be used directly. - bool canUseUntranslated = false; - if ( m_lang.empty() ) - { - // If we are using the default language, check if the message ID - // language is acceptable for this system. - const wxString domain_lang = GetBestTranslation(domain, msgIdLang); - - if ( msgIdLang == domain_lang ) - canUseUntranslated = true; - } - else // But if we have a fixed language, we should just check it instead. - { - // Consider message IDs for another region using the same language - // acceptable. - if ( msgIdLang.BeforeFirst('_') == m_lang.BeforeFirst('_') ) - canUseUntranslated = true; - } - - if ( canUseUntranslated ) + if ( LoadCatalog(domain, domain_lang) ) { wxLogTrace(TRACE_I18N, - wxS("not using translations for domain '%s' with msgid language '%s'"), - domain, msgIdLang); - return true; + wxS("adding '%s' translation for domain '%s' (msgid language '%s')"), + domain_lang, domain, msgIdLang); + return Translations::Found; } - return false; + // LoadCatalog() failed, but GetBestTranslation() returned non-empty language. + // That must mean that msgIdLanguage was used. + wxLogTrace(TRACE_I18N, + wxS("not using translations for domain '%s' with msgid language '%s'"), + domain, msgIdLang); + return Translations::NotNeeded; } @@ -1452,23 +1429,19 @@ wxString wxTranslations::GetBestTranslation(const wxString& domain, wxString wxTranslations::GetBestTranslation(const wxString& domain, const wxString& msgIdLanguage) { - wxString lang = GetBestAvailableTranslation(domain); + // Determine the best language, including the msgId language, which is always + // available because it is present in the code: + wxString lang = DoGetBestAvailableTranslation(domain, msgIdLanguage); + if ( lang.empty() ) { - wxArrayString available; - available.push_back(msgIdLanguage); - available.push_back(msgIdLanguage.BeforeFirst('_')); - lang = GetPreferredUILanguage(available); - if ( lang.empty() ) - { - wxLogTrace(TRACE_I18N, - "no available language for domain '%s'", domain); - } - else - { - wxLogTrace(TRACE_I18N, - "using message ID language '%s' for domain '%s'", lang); - } + wxLogTrace(TRACE_I18N, + "no available language for domain '%s'", domain); + } + else if ( lang == msgIdLanguage || lang == msgIdLanguage.BeforeFirst('_') ) + { + wxLogTrace(TRACE_I18N, + "using message ID language '%s' for domain '%s'", lang, domain); } return lang; @@ -1476,7 +1449,21 @@ wxString wxTranslations::GetBestTranslation(const wxString& domain, wxString wxTranslations::GetBestAvailableTranslation(const wxString& domain) { - const wxArrayString available(GetAvailableTranslations(domain)); + // Determine the best language from the ones with actual translation file: + // As this function never considers the language of the original messages as being + // available, pass empty string as message ID language to the helper function. + return DoGetBestAvailableTranslation(domain, wxString()); +} + +wxString wxTranslations::DoGetBestAvailableTranslation(const wxString& domain, const wxString& additionalAvailableLanguage) +{ + wxArrayString available(GetAvailableTranslations(domain)); + if ( !additionalAvailableLanguage.empty() ) + { + available.push_back(additionalAvailableLanguage); + available.push_back(additionalAvailableLanguage.BeforeFirst('_')); + } + if ( !m_lang.empty() ) { wxLogTrace(TRACE_I18N, diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index 014d32b678..e52fa2d833 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -24,6 +24,9 @@ #include "wx/arrstr.h" #include "wx/intl.h" +#include "wx/log.h" +#include "wx/tokenzr.h" +#include "wx/utils.h" #ifndef __WINDOWS__ #include "wx/language.h" @@ -31,6 +34,8 @@ #include "wx/private/uilocale.h" +#define TRACE_I18N wxS("i18n") + // ---------------------------------------------------------------------------- // helper functions // ---------------------------------------------------------------------------- @@ -678,7 +683,7 @@ int wxUILocale::GetSystemLanguage() { const wxLanguageInfos& languagesDB = wxGetLanguageInfos(); size_t count = languagesDB.size(); - wxVector preferred = wxUILocaleImpl::GetPreferredUILanguages(); + wxVector preferred = wxUILocale::GetPreferredUILanguages(); for (wxVector::const_iterator j = preferred.begin(); j != preferred.end(); @@ -743,6 +748,29 @@ int wxUILocale::GetSystemLocale() /* static */ wxVector wxUILocale::GetPreferredUILanguages() { + // The WXLANGUAGE variable may contain a colon separated list of language + // codes in the order of preference. It is modelled after GNU's LANGUAGE: + // http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html + wxString languageFromEnv; + if (wxGetEnv("WXLANGUAGE", &languageFromEnv) && !languageFromEnv.empty()) + { + wxVector preferred; + wxStringTokenizer tknzr(languageFromEnv, ":"); + while (tknzr.HasMoreTokens()) + { + const wxString tok = tknzr.GetNextToken(); + if (const wxLanguageInfo* li = wxUILocale::FindLanguageInfo(tok)) + { + preferred.push_back(li->CanonicalName); + } + } + if (!preferred.empty()) + { + wxLogTrace(TRACE_I18N, " - using languages override from WXLANGUAGE: '%s'", languageFromEnv); + return preferred; + } + } + return wxUILocaleImpl::GetPreferredUILanguages(); } diff --git a/tests/Makefile.in b/tests/Makefile.in index 760d178820..f9c14e47b6 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -463,7 +463,7 @@ COND_MONOLITHIC_1___WXLIB_MONO_p = \ ### Targets: ### -all: test$(EXEEXT) $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_gui_bundle___depname) $(__test_allheaders___depname) $(__test_allheaders_bundle___depname) data data-image-sample data-images fr ja +all: test$(EXEEXT) $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_gui_bundle___depname) $(__test_allheaders___depname) $(__test_allheaders_bundle___depname) data data-image-sample data-images en_GB fr ja install: @@ -602,6 +602,18 @@ data-images: esac; \ done +en_GB: + @mkdir -p ./intl/en_GB + @for f in internat.po internat.mo; do \ + if test ! -f ./intl/en_GB/$$f -a ! -d ./intl/en_GB/$$f ; \ + then x=yep ; \ + else x=`find $(srcdir)/intl/en_GB/$$f -newer ./intl/en_GB/$$f -print` ; \ + fi; \ + case "$$x" in ?*) \ + cp -pRf $(srcdir)/intl/en_GB/$$f ./intl/en_GB ;; \ + esac; \ + done + fr: @mkdir -p ./intl/fr @for f in internat.po internat.mo; do \ @@ -1278,4 +1290,4 @@ failtest_allheaders: @IF_GNU_MAKE@-include ./.deps/*.d .PHONY: all install uninstall clean distclean test_gui_bundle \ - test_allheaders_bundle data data-image-sample data-images fr ja + test_allheaders_bundle data data-image-sample data-images en_GB fr ja diff --git a/tests/intl/en_GB/internat.mo b/tests/intl/en_GB/internat.mo new file mode 100644 index 0000000000..2089093322 Binary files /dev/null and b/tests/intl/en_GB/internat.mo differ diff --git a/tests/intl/en_GB/internat.po b/tests/intl/en_GB/internat.po new file mode 100644 index 0000000000..1bdced1767 --- /dev/null +++ b/tests/intl/en_GB/internat.po @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2003-10-04 23:10+0200\n" +"PO-Revision-Date: 2024-02-13 13:25+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 9b14175fce..a504a9edaa 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -19,6 +19,7 @@ #include "wx/intl.h" #include "wx/uilocale.h" +#include "wx/scopeguard.h" #include "wx/private/glibc.h" @@ -241,8 +242,9 @@ void IntlTestCase::IsAvailable() TEST_CASE("wxTranslations::AddCatalog", "[translations]") { - // We currently have translations for French and Japanese in this test - // directory, check that loading those succeeds but loading others doesn't. + // We currently have translations for British English, French and Japanese + // in this test directory, check that loading those succeeds but loading + // others doesn't. wxFileTranslationsLoader::AddCatalogLookupPathPrefix("./intl"); const wxString domain("internat"); @@ -252,11 +254,12 @@ TEST_CASE("wxTranslations::AddCatalog", "[translations]") SECTION("All") { auto available = trans.GetAvailableTranslations(domain); - REQUIRE( available.size() == 2 ); + REQUIRE( available.size() == 3 ); available.Sort(); - CHECK( available[0] == "fr" ); - CHECK( available[1] == "ja" ); + CHECK( available[0] == "en_GB" ); + CHECK( available[1] == "fr" ); + CHECK( available[2] == "ja" ); } SECTION("French") @@ -287,6 +290,60 @@ TEST_CASE("wxTranslations::AddCatalog", "[translations]") } } +TEST_CASE("wxTranslations::GetBestTranslation", "[translations]") +{ + wxFileTranslationsLoader::AddCatalogLookupPathPrefix("./intl"); + + const wxString domain("internat"); + + wxTranslations trans; + wxON_BLOCK_EXIT1( wxUnsetEnv, "WXLANGUAGE" ); + + SECTION("ChooseLanguage") + { + // Simple case. + wxSetEnv("WXLANGUAGE", "fr:en"); + CHECK( trans.GetBestTranslation(domain) == "fr" ); + CHECK( trans.GetBestAvailableTranslation(domain) == "fr" ); + + // Choose 2nd language _and_ its base form. + wxSetEnv("WXLANGUAGE", "cs:fr_CA:en"); + CHECK( trans.GetBestTranslation(domain) == "fr" ); + CHECK( trans.GetBestAvailableTranslation(domain) == "fr" ); + } + + SECTION("EnglishHandling") + { + // Check that existing en_GB file isn't used for msgid language. + wxSetEnv("WXLANGUAGE", "en_US"); + + CHECK( trans.GetBestTranslation(domain) == "en" ); + // GetBestAvailableTranslation() will wrongly return "en_GB", don't test that. + + wxSetEnv("WXLANGUAGE", "es:en"); + CHECK( trans.GetBestTranslation(domain) == "en" ); + // GetBestAvailableTranslation() will wrongly return "en_GB", don't test that. + + // And that it is used when it should be + wxSetEnv("WXLANGUAGE", "en_GB"); + CHECK( trans.GetBestTranslation(domain) == "en_GB" ); + CHECK( trans.GetBestAvailableTranslation(domain) == "en_GB" ); + + } + + SECTION("DontSkipMsgidLanguage") + { + // Check that msgid language will be used if it's the best match. + wxSetEnv("WXLANGUAGE", "cs:en:fr"); + CHECK( trans.GetBestTranslation(domain) == "en" ); + + // ...But won't be used if there's a suitable translation file. + wxSetEnv("WXLANGUAGE", "fr:en:cs"); + CHECK( trans.GetBestTranslation(domain) == "fr" ); + CHECK( trans.GetBestAvailableTranslation(domain) == "fr" ); + } +} + TEST_CASE("wxLocale::Default", "[locale]") { const int langDef = wxUILocale::GetSystemLanguage(); diff --git a/tests/makefile.gcc b/tests/makefile.gcc index 048f3fc3ed..8cbaed0a1e 100644 --- a/tests/makefile.gcc +++ b/tests/makefile.gcc @@ -512,7 +512,7 @@ $(OBJS): ### Targets: ### -all: $(OBJS)\test.exe $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_allheaders___depname) data data-image-sample data-images fr ja +all: $(OBJS)\test.exe $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_allheaders___depname) data data-image-sample data-images en_GB fr ja clean: -if exist $(OBJS)\*.o del $(OBJS)\*.o @@ -572,6 +572,10 @@ data-images: if not exist image mkdir image for %%f in (bitfields.bmp bitfields-alpha.bmp 8bpp-colorsused-large.bmp 8bpp-colorsused-negative.bmp rle4-delta-320x240.bmp rle8-delta-320x240.bmp rle8-delta-320x240-expected.bmp horse_grey.bmp horse_grey_flipped.bmp horse_rle4.bmp horse_rle4_flipped.bmp horse_rle8.bmp horse_rle8_flipped.bmp horse_bicubic_50x50.png horse_bicubic_100x100.png horse_bicubic_150x150.png horse_bicubic_300x300.png horse_bilinear_50x50.png horse_bilinear_100x100.png horse_bilinear_150x150.png horse_bilinear_300x300.png horse_box_average_50x50.png horse_box_average_100x100.png horse_box_average_150x150.png horse_box_average_300x300.png cross_bicubic_256x256.png cross_bilinear_256x256.png cross_box_average_256x256.png cross_nearest_neighb_256x256.png paste_input_background.png paste_input_black.png paste_input_overlay_transparent_border_opaque_square.png paste_input_overlay_transparent_border_semitransparent_circle.png paste_input_overlay_transparent_border_semitransparent_square.png paste_result_background_plus_circle_plus_square.png paste_result_background_plus_overlay_transparent_border_opaque_square.png paste_result_background_plus_overlay_transparent_border_semitransparent_square.png paste_result_no_background_square_over_circle.png wx.png toucan.png toucan_hue_0.538.png toucan_sat_-0.41.png toucan_bright_-0.259.png toucan_hsv_0.538_-0.41_-0.259.png toucan_light_46.png toucan_dis_240.png toucan_grey.png toucan_mono_255_255_255.png width-times-height-overflow.bmp width_height_32_bit_overflow.pgm bad_truncated.gif) do if not exist image\%%f copy .\image\%%f image +en_GB: + if not exist $(OBJS)\intl\en_GB mkdir $(OBJS)\intl\en_GB + for %%f in (internat.po internat.mo) do if not exist $(OBJS)\intl\en_GB\%%f copy .\intl\en_GB\%%f $(OBJS)\intl\en_GB + fr: if not exist $(OBJS)\intl\fr mkdir $(OBJS)\intl\fr for %%f in (internat.po internat.mo) do if not exist $(OBJS)\intl\fr\%%f copy .\intl\fr\%%f $(OBJS)\intl\fr @@ -1201,7 +1205,7 @@ $(OBJS)\test_allheaders_allheaders.o: ./allheaders.cpp $(OBJS)\test_allheaders_testableframe.o: ./testableframe.cpp $(CXX) -c -o $@ $(TEST_ALLHEADERS_CXXFLAGS) $(CPPDEPS) $< -.PHONY: all clean data data-image-sample data-images fr ja +.PHONY: all clean data data-image-sample data-images en_GB fr ja SHELL := $(COMSPEC) diff --git a/tests/makefile.vc b/tests/makefile.vc index 41c1452831..2ee5ebb7d3 100644 --- a/tests/makefile.vc +++ b/tests/makefile.vc @@ -792,7 +792,7 @@ $(OBJS): ### Targets: ### -all: $(OBJS)\test.exe $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_allheaders___depname) data data-image-sample data-images fr ja +all: $(OBJS)\test.exe $(__test_drawing___depname) $(__test_drawingplugin___depname) $(__test_gui___depname) $(__test_allheaders___depname) data data-image-sample data-images en_GB fr ja clean: -if exist $(OBJS)\*.obj del $(OBJS)\*.obj @@ -863,6 +863,10 @@ fr: if not exist $(OBJS)\intl\fr mkdir $(OBJS)\intl\fr for %f in (internat.po internat.mo) do if not exist $(OBJS)\intl\fr\%f copy .\intl\fr\%f $(OBJS)\intl\fr +en_GB: + if not exist $(OBJS)\intl\en_GB mkdir $(OBJS)\intl\en_GB + for %f in (internat.po internat.mo) do if not exist $(OBJS)\intl\en_GB\%f copy .\intl\en_GB\%f $(OBJS)\intl\en_GB + ja: if not exist $(OBJS)\intl\ja mkdir $(OBJS)\intl\ja for %f in (internat.po internat.mo) do if not exist $(OBJS)\intl\ja\%f copy .\intl\ja\%f $(OBJS)\intl\ja diff --git a/tests/test.bkl b/tests/test.bkl index 67ce2c10c1..323ce14c9d 100644 --- a/tests/test.bkl +++ b/tests/test.bkl @@ -429,6 +429,7 @@ internat.po internat.mo +