From: Bruno Haible Date: Wed, 12 Feb 2025 09:54:08 +0000 (+0100) Subject: examples: Add hello-c-http example. X-Git-Tag: v0.24~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=31554caccc04b3b7203ed116a9b36702f49151c1;p=thirdparty%2Fgettext.git examples: Add hello-c-http example. * gettext-tools/examples/hello-c-http/INSTALL: New file. * gettext-tools/examples/hello-c-http/autogen.sh: New file, based on gettext-tools/examples/hello-c/autogen.sh. * gettext-tools/examples/hello-c-http/autoclean.sh: New file, based on gettext-tools/examples/hello-c/autoclean.sh. * gettext-tools/examples/hello-c-http/hello-server.c: New file. * gettext-tools/examples/hello-c-http/Makefile.am: New file, based on gettext-tools/examples/hello-c/Makefile.am. * gettext-tools/examples/hello-c-http/configure.ac: New file, based on gettext-tools/examples/hello-c/configure.ac. * gettext-tools/examples/hello-c-http/m4/Makefile.am: New file, copied from gettext-tools/examples/hello-c/m4/Makefile.am. * gettext-tools/examples/hello-c-http/po/POTFILES.in: New file. * gettext-tools/examples/hello-c-http/po/Makevars: New file, copied from gettext-tools/examples/hello-c/po/Makevars. * gettext-tools/examples/hello-c-http/po/LINGUAS: New file, copied from gettext-tools/examples/hello-c/po/LINGUAS. * gettext-tools/examples/Makefile.am (EXAMPLESFILES, EXAMPLESDIRS): Add hello-c-http. * gettext-tools/examples/po/Makefile.am (POTFILES, SMALLPOTS): Update for hello-c-http. (hello-c-http.pot): New target. (SMALLPOFILES_FOR_lang): Update for hello-c-http. ($(srcdir)/../hello-c-http/po/$(LL).po): New rule. * gettext-tools/examples/check-examples (func_check_autoclean_all, func_check_distclean_all, func_check_maintainerclean_all, func_check_maintainerclean_vpath_all, func_check_dist_all, func_check_dist_vpath_all, func_check_install_all, func_check_uninstall_all, func_check_distcheck_all, func_check_all): Handle hello-c-http as well. * gettext-tools/examples/README: Mention hello-c-http. * NEWS: Likewise. --- diff --git a/NEWS b/NEWS index 507da43b1..0d509e761 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Version 0.24 - January 2025 +Version 0.24 - February 2025 # Programming languages support: * JavaScript: @@ -8,6 +8,9 @@ Version 0.24 - January 2025 - 'msgfmt -c' now verifies the syntax of translations of Rust format strings. - A new example 'hello-rust' has been added. + * C: + - A new example 'hello-c-http' has been added, showing the use of + GNU gettext in a multithreaded web server. * C++: - A new example 'hello-c++-gnome3' has been added. * Ruby: diff --git a/gettext-tools/examples/Makefile.am b/gettext-tools/examples/Makefile.am index 0559321e5..82ceeadb3 100644 --- a/gettext-tools/examples/Makefile.am +++ b/gettext-tools/examples/Makefile.am @@ -72,6 +72,16 @@ EXAMPLESFILES = \ hello-c-gnome3/po/Makevars \ hello-c-gnome3/po/POTFILES.in \ \ + hello-c-http/INSTALL \ + hello-c-http/autogen.sh \ + hello-c-http/autoclean.sh \ + hello-c-http/hello-server.c \ + hello-c-http/Makefile.am \ + hello-c-http/configure.ac \ + hello-c-http/m4/Makefile.am \ + hello-c-http/po/Makevars \ + hello-c-http/po/POTFILES.in \ + \ hello-c++/INSTALL \ hello-c++/autogen.sh \ hello-c++/autoclean.sh \ @@ -424,6 +434,7 @@ EXAMPLESDIRS = \ hello-c \ hello-c-gnome2 \ hello-c-gnome3 \ + hello-c-http \ hello-c++ \ hello-c++20 \ hello-c++-qt \ diff --git a/gettext-tools/examples/README b/gettext-tools/examples/README index 04d81b2d3..8505cef68 100644 --- a/gettext-tools/examples/README +++ b/gettext-tools/examples/README @@ -8,6 +8,7 @@ environment. hello-c C hello-c-gnome2 C GNOME 2 (obsolete) hello-c-gnome3 C GNOME 3.10 or later + hello-c-http C web browser hello-c++ C++ hello-c++20 C++ 20 hello-c++-qt C++ Qt @@ -60,6 +61,7 @@ Makefile types: hello-c .gmo Makefile.in.in hello-c-gnome2 .gmo Makefile.in.in hello-c-gnome3 .gmo Makefile.in.in + hello-c-http .gmo Makefile.in.in hello-c++ .gmo Makefile.in.in hello-c++20 .gmo Makefile.in.in hello-c++-kde .gmo Makefile.in.in diff --git a/gettext-tools/examples/check-examples b/gettext-tools/examples/check-examples index b35c2b379..df3e2188f 100755 --- a/gettext-tools/examples/check-examples +++ b/gettext-tools/examples/check-examples @@ -131,6 +131,7 @@ func_check_autoclean_all () func_check_autoclean hello-c func_check_autoclean hello-c-gnome2 func_check_autoclean hello-c-gnome3 + func_check_autoclean hello-c-http func_check_autoclean hello-c++ func_check_autoclean hello-c++20 func_check_autoclean hello-c++-qt @@ -206,6 +207,7 @@ func_check_distclean_all () func_check_distclean hello-c #func_check_distclean hello-c-gnome2 #func_check_distclean hello-c-gnome3 + func_check_distclean hello-c-http func_check_distclean hello-c++ func_check_distclean hello-c++20 #func_check_distclean hello-c++-qt @@ -283,6 +285,7 @@ func_check_maintainerclean_all () func_check_maintainerclean hello-c #func_check_maintainerclean hello-c-gnome2 #func_check_maintainerclean hello-c-gnome3 + func_check_maintainerclean hello-c-http func_check_maintainerclean hello-c++ func_check_maintainerclean hello-c++20 #func_check_maintainerclean hello-c++-qt @@ -363,6 +366,7 @@ func_check_maintainerclean_vpath_all () func_check_maintainerclean_vpath hello-c #func_check_maintainerclean_vpath hello-c-gnome2 #func_check_maintainerclean_vpath hello-c-gnome3 + func_check_maintainerclean_vpath hello-c-http func_check_maintainerclean_vpath hello-c++ func_check_maintainerclean_vpath hello-c++20 #func_check_maintainerclean_vpath hello-c++-qt @@ -450,6 +454,7 @@ func_check_dist_all () func_check_dist hello-c #func_check_dist hello-c-gnome2 #func_check_dist hello-c-gnome3 + func_check_dist hello-c-http func_check_dist hello-c++ func_check_dist hello-c++20 #func_check_dist hello-c++-qt @@ -537,6 +542,7 @@ func_check_dist_vpath_all () func_check_dist_vpath hello-c #func_check_dist_vpath hello-c-gnome2 #func_check_dist_vpath hello-c-gnome3 + func_check_dist_vpath hello-c-http func_check_dist_vpath hello-c++ func_check_dist_vpath hello-c++20 #func_check_dist_vpath hello-c++-qt @@ -616,6 +622,7 @@ func_check_install_all () func_check_install hello-c #func_check_install hello-c-gnome2 #func_check_install hello-c-gnome3 + func_check_install hello-c-http func_check_install hello-c++ func_check_install hello-c++20 #func_check_install hello-c++-qt @@ -692,6 +699,7 @@ func_check_uninstall_all () func_check_uninstall hello-c #func_check_uninstall hello-c-gnome2 #func_check_uninstall hello-c-gnome3 + func_check_uninstall hello-c-http func_check_uninstall hello-c++ func_check_uninstall hello-c++20 #func_check_uninstall hello-c++-qt @@ -770,6 +778,7 @@ func_check_distcheck_all () func_check_distcheck hello-c #func_check_distcheck hello-c-gnome2 #func_check_distcheck hello-c-gnome3 + func_check_distcheck hello-c-http func_check_distcheck hello-c++ func_check_distcheck hello-c++20 #func_check_distcheck hello-c++-qt @@ -831,6 +840,7 @@ func_check_all () func_check hello-c func_check hello-c-gnome2 func_check hello-c-gnome3 + func_check hello-c-http func_check hello-c++ func_check hello-c++20 func_check hello-c++-qt diff --git a/gettext-tools/examples/hello-c-http/INSTALL b/gettext-tools/examples/hello-c-http/INSTALL new file mode 100644 index 000000000..33dd0ac90 --- /dev/null +++ b/gettext-tools/examples/hello-c-http/INSTALL @@ -0,0 +1,83 @@ +This example implements a simple multithreaded web server. + +Platforms +--------- + +It supports systems with GNU libc. +It may also work, with some adaptations: +on Unix platforms (which have POSIX threads) +other than NetBSD (which does not have uselocale() nor gettext_l). + +Dependencies +------------ + +It relies just on gettext-runtime (and libc, of course). + +Preparations +------------ + +To install the needed locales on glibc systems: + +$ sudo localedef -i af_ZA -f UTF-8 af_ZA.UTF-8 +$ sudo localedef -i ast_ES -f UTF-8 ast_ES.UTF-8 +$ sudo localedef -i bg_BG -f UTF-8 bg_BG.UTF-8 +$ sudo localedef -i ca_ES -f UTF-8 ca_ES.UTF-8 +$ sudo localedef -i cs_CZ -f UTF-8 cs_CZ.UTF-8 +$ sudo localedef -i da_DK -f UTF-8 da_DK.UTF-8 +$ sudo localedef -i de_DE -f UTF-8 de_DE.UTF-8 +$ sudo localedef -i el_GR -f UTF-8 el_GR.UTF-8 +$ sudo localedef -i en_US -f UTF-8 en_US.UTF-8 +$ sudo localedef -i eo -f UTF-8 eo +$ sudo localedef -i es_ES -f UTF-8 es_ES.UTF-8 +$ sudo localedef -i fi_FI -f UTF-8 fi_FI.UTF-8 +$ sudo localedef -i fr_FR -f UTF-8 fr_FR.UTF-8 +$ sudo localedef -i ga_IE -f UTF-8 ga_IE.UTF-8 +$ sudo localedef -i gl_ES -f UTF-8 gl_ES.UTF-8 +$ sudo localedef -i hr_HR -f UTF-8 hr_HR.UTF-8 +$ sudo localedef -i hu_HU -f UTF-8 hu_HU.UTF-8 +$ sudo localedef -i id_ID -f UTF-8 id_ID.UTF-8 +$ sudo localedef -i it_IT -f UTF-8 it_IT.UTF-8 +$ sudo localedef -i ja_JP -f UTF-8 ja_JP.UTF-8 +$ sudo localedef -i ka_GE -f UTF-8 ka_GE.UTF-8 +$ sudo localedef -i ky_KG -f UTF-8 ky_KG +$ sudo localedef -i lv_LV -f UTF-8 lv_LV.UTF-8 +$ sudo localedef -i ms_MY -f UTF-8 ms_MY.UTF-8 +$ sudo localedef -i mt_MT -f UTF-8 mt_MT.UTF-8 +$ sudo localedef -i nb_NO -f UTF-8 nb_NO.UTF-8 +$ sudo localedef -i nl_NL -f UTF-8 nl_NL.UTF-8 +$ sudo localedef -i nn_NO -f UTF-8 nn_NO.UTF-8 +$ sudo localedef -i pl_PL -f UTF-8 pl_PL.UTF-8 +$ sudo localedef -i pt_PT -f UTF-8 pt_PT.UTF-8 +$ sudo localedef -i pt_BR -f UTF-8 pt_BR.UTF-8 +$ sudo localedef -i ro_RO -f UTF-8 ro_RO.UTF-8 +$ sudo localedef -i ru_RU -f UTF-8 ru_RU.UTF-8 +$ sudo localedef -i sk_SK -f UTF-8 sk_SK.UTF-8 +$ sudo localedef -i sl_SI -f UTF-8 sl_SI.UTF-8 +$ sudo localedef -i sq_AL -f UTF-8 sq_AL.UTF-8 +$ sudo localedef -i sr_RS -f UTF-8 sr_RS +$ sudo localedef -i sv_SE -f UTF-8 sv_SE.UTF-8 +$ sudo localedef -i ta_IN -f UTF-8 ta_IN +$ sudo localedef -i tr_TR -f UTF-8 tr_TR.UTF-8 +$ sudo localedef -i uk_UA -f UTF-8 uk_UA.UTF-8 +$ sudo localedef -i vi_VN -f UTF-8 vi_VN +$ sudo localedef -i zh_CN -f UTF-8 zh_CN.UTF-8 +$ sudo localedef -i zh_HK -f UTF-8 zh_HK.UTF-8 +$ sudo localedef -i zh_TW -f UTF-8 zh_TW.UTF-8 + +On Debian and Debian-based systems, if you want these locales to be +persistent across automatic system updates, the approach is different: +There, you need to enable the locales in the file /etc/locale.gen and +then run +$ sudo locale-gen + +Building +-------- + +Installation: + ./autogen.sh + ./configure --prefix=/some/prefix + make + make install +Cleanup: + make distclean + ./autoclean.sh diff --git a/gettext-tools/examples/hello-c-http/Makefile.am b/gettext-tools/examples/hello-c-http/Makefile.am new file mode 100644 index 000000000..4409b2faf --- /dev/null +++ b/gettext-tools/examples/hello-c-http/Makefile.am @@ -0,0 +1,26 @@ +# Example for use of GNU gettext. +# This file is in the public domain. +# +# Makefile configuration - processed by automake. + +# General automake options. +AUTOMAKE_OPTIONS = foreign no-dependencies +ACLOCAL_AMFLAGS = -I m4 + +# The list of subdirectories containing Makefiles. +SUBDIRS = m4 po + +# The list of programs that are built. +bin_PROGRAMS = hello-server + +# The source files of the 'hello-server' program. +hello_server_SOURCES = hello-server.c + +# Define a C macro LOCALEDIR indicating where catalogs will be installed. +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ + +# Link time dependencies. +LDADD = @LIBINTL@ + +# Additional files to be distributed. +EXTRA_DIST = autogen.sh autoclean.sh diff --git a/gettext-tools/examples/hello-c-http/autoclean.sh b/gettext-tools/examples/hello-c-http/autoclean.sh new file mode 100755 index 000000000..9b8332fb2 --- /dev/null +++ b/gettext-tools/examples/hello-c-http/autoclean.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Example for use of GNU gettext. +# This file is in the public domain. +# +# Script for cleaning all autogenerated files. + +test ! -f Makefile || make distclean +rm -rf autom4te.cache + +# Brought in by autopoint. +rm -f ABOUT-NLS +rm -f config.rpath +rm -f m4/gettext.m4 +rm -f m4/build-to-host.m4 +rm -f m4/host-cpu-c-abi.m4 +rm -f m4/iconv.m4 +rm -f m4/intlmacosx.m4 +rm -f m4/lib-ld.m4 +rm -f m4/lib-link.m4 +rm -f m4/lib-prefix.m4 +rm -f m4/nls.m4 +rm -f m4/po.m4 +rm -f m4/progtest.m4 +rm -f po/Makefile.in.in +rm -f po/remove-potcdate.sed + +# Generated by aclocal. +rm -f aclocal.m4 + +# Generated by autoconf. +rm -f configure + +# Generated or brought in by automake. +rm -f Makefile.in +rm -f m4/Makefile.in +rm -f compile +rm -f install-sh +rm -f missing +rm -f config.guess +rm -f config.sub +rm -f po/*.pot +rm -f po/stamp-po +rm -f po/*.gmo diff --git a/gettext-tools/examples/hello-c-http/autogen.sh b/gettext-tools/examples/hello-c-http/autogen.sh new file mode 100755 index 000000000..c589e7d1e --- /dev/null +++ b/gettext-tools/examples/hello-c-http/autogen.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# Example for use of GNU gettext. +# This file is in the public domain. +# +# Script for regenerating all autogenerated files. + +if test -r ../Makefile.am; then + # Inside the gettext source directory. + GETTEXT_TOPSRCDIR=../../.. +else + if test -r ../Makefile; then + # Inside a gettext build directory. + GETTEXT_TOOLS_SRCDIR=`sed -n -e 's,^top_srcdir *= *\(.*\)$,\1,p' ../Makefile` + # Adjust a relative top_srcdir. + case $GETTEXT_TOOLS_SRCDIR in + /*) ;; + *) GETTEXT_TOOLS_SRCDIR=../$GETTEXT_TOOLS_SRCDIR ;; + esac + GETTEXT_TOPSRCDIR=$GETTEXT_TOOLS_SRCDIR/../.. + else + # Installed under ${prefix}/share/doc/gettext/examples. + . ../installpaths + fi +fi + +autopoint -f # was: gettextize -f -c +rm po/Makevars.template +rm po/Rules-quot +rm po/boldquot.sed +rm po/en@boldquot.header +rm po/en@quot.header +rm po/insert-header.sed +rm po/quot.sed + +aclocal -I m4 + +autoconf + +automake -a -c + +cd po +for f in *.po; do + if test -r "$f"; then + lang=`echo $f | sed -e 's,\.po$,,'` + msgfmt -c -o $lang.gmo $lang.po + fi +done +cd .. diff --git a/gettext-tools/examples/hello-c-http/configure.ac b/gettext-tools/examples/hello-c-http/configure.ac new file mode 100644 index 000000000..b7f4aef1a --- /dev/null +++ b/gettext-tools/examples/hello-c-http/configure.ac @@ -0,0 +1,17 @@ +dnl Example for use of GNU gettext. +dnl This file is in the public domain. +dnl +dnl Configuration file - processed by autoconf. + +AC_INIT([hello-c-http], [0]) +AC_CONFIG_SRCDIR([hello-server.c]) +AM_INIT_AUTOMAKE([1.11]) + +AC_PROG_CC +AM_GNU_GETTEXT([external]) +AM_GNU_GETTEXT_VERSION([0.23]) + +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([m4/Makefile]) +AC_CONFIG_FILES([po/Makefile.in]) +AC_OUTPUT diff --git a/gettext-tools/examples/hello-c-http/hello-server.c b/gettext-tools/examples/hello-c-http/hello-server.c new file mode 100644 index 000000000..c4bffc522 --- /dev/null +++ b/gettext-tools/examples/hello-c-http/hello-server.c @@ -0,0 +1,429 @@ +/* Example for use of GNU gettext. + This file is in the public domain. + + Source code of the C program. */ + +/* This example implements a simple multithreaded web server. + + In order to get translations via gettext(), a locale must be installed on + the server system for each language that should be served. For example, + in order to get French translations, you need to install the fr_FR.UTF-8 + locale. You find the list of locales below and the installation instructions + in the INSTALL file. + This may seem strange to people who think "why is this necessary? why should + reading a .mo file need a locale?" The rationale is that servers who produce + French text for a web page most often also need French number formatting, + French sorting (for lists and UI elements), etc. — and these functionalities + rely on the locale. + + Since the server is multithreaded, different requests may be served in + different threads. And different requests can come from different users, + that have declared different language preferences in their web browser. + Therefore, while at a certain moment one thread may produce a French + translation (and thus work with a French locale), another thread may be + producing a Spanish translation (and thus work with a Spanish locale) + at the same time. + Using the global locale (via setlocale()) would require locking, so that + different threads don't influence each other; but this would severely limit + the possible throughput of the server (which is the motivation for making + the server multithreaded in the first place). + Therefore the server does not use setlocale(), but instead works with + locale_t objects, that can become the "current locale" of a thread, via + uselocale(). + + While it would be possible to allocate the locale_t objects lazily (upon + the first request that needs the particular locale), here we allocate + them all up-front, so that the response time for a given request is fast + and so that there is no contention between the threads. */ + + +/* Persuade glibc to declare asprintf(). */ +#define _GNU_SOURCE 1 + +/* Get textdomain(), bindtextdomain(), gettext() declarations. */ +#include + +/* Get locale_t, newlocale(), uselocale() declarations. */ +#include + +/* Get pthread_create(), pthread_join() declarations. */ +#include + +/* Get asprintf(), dprintf() declarations. */ +#include + +/* Get abort(), free() declarations. */ +#include + +/* Get memset(), strchr(), strcasecmp(), strncasecmp() declarations. */ +#include + +/* Get nanosleep(). */ +#include + +/* Get close() declaration. */ +#include + +/* Get socket(), setsockopt(), bind(), listen(), accept(), recv() declarations. */ +#include + +/* Get IPPROTO_TCP, INADDR_ANY, in6addr_any. */ +#include + + +/* Mapping from language to locale. */ +struct language_support +{ + const char *language; + const char *locale_name; + locale_t locale; /* NULL when the locale is not installed on the system */ +}; +static struct language_support all_languages[] = +{ + /* The locale names here must all be UTF-8 locales. */ + { "af", "af_ZA.UTF-8" }, + { "ast", "ast_ES.UTF-8" }, + { "bg", "bg_BG.UTF-8" }, + { "ca", "ca_ES.UTF-8" }, + { "cs", "cs_CZ.UTF-8" }, + { "da", "da_DK.UTF-8" }, + { "de", "de_DE.UTF-8" }, + { "el", "el_GR.UTF-8" }, + { "en", "en_US.UTF-8" }, + { "eo", "eo" }, + { "es", "es_ES.UTF-8" }, + { "fi", "fi_FI.UTF-8" }, + { "fr", "fr_FR.UTF-8" }, + { "ga", "ga_IE.UTF-8" }, + { "gl", "gl_ES.UTF-8" }, + { "hr", "hr_HR.UTF-8" }, + { "hu", "hu_HU.UTF-8" }, + { "id", "id_ID.UTF-8" }, + { "it", "it_IT.UTF-8" }, + { "ja", "ja_JP.UTF-8" }, + { "ka", "ka_GE.UTF-8" }, + { "ky", "ky_KG" }, + { "lv", "lv_LV.UTF-8" }, + { "ms", "ms_MY.UTF-8" }, + { "mt", "mt_MT.UTF-8" }, + { "nb", "nb_NO.UTF-8" }, + { "nl", "nl_NL.UTF-8" }, + { "nn", "nn_NO.UTF-8" }, + { "pl", "pl_PL.UTF-8" }, + { "pt", "pt_PT.UTF-8" }, + { "pt_BR", "pt_BR.UTF-8" }, + { "ro", "ro_RO.UTF-8" }, + { "ru", "ru_RU.UTF-8" }, + { "sk", "sk_SK.UTF-8" }, + { "sl", "sl_SI.UTF-8" }, + { "sq", "sq_AL.UTF-8" }, + { "sr", "sr_RS" }, + { "sv", "sv_SE.UTF-8" }, + { "ta", "ta_IN" }, + { "tr", "tr_TR.UTF-8" }, + { "uk", "uk_UA.UTF-8" }, + { "vi", "vi_VN" }, + { "zh_CN", "zh_CN.UTF-8" }, + { "zh_HK", "zh_HK.UTF-8" }, + { "zh_TW", "zh_TW.UTF-8" } +}; + +/* Get the locale that exactly matches a given language. */ +static locale_t +get_locale_from_language (const char *language) +{ + size_t i; + for (i = 0; i < sizeof (all_languages) / sizeof (all_languages[0]); i++) + if (strcasecmp (all_languages[i].language, language) == 0) + return all_languages[i].locale; + return NULL; +} + +/* Get the locale that can be used for a given language. */ +static locale_t +get_locale_matching_language (char *language) +{ + /* Convert '-' to '_'. */ + char *dash = strchr (language, '-'); + if (dash != NULL) + *dash = '_'; + + locale_t result = get_locale_from_language (language); + if (result == NULL && dash != NULL) + { + /* Truncate the language at the dash's position. */ + *dash = '\0'; + result = get_locale_from_language (language); + } + + /* Restore language. */ + if (dash != NULL) + *dash = '-'; + + return result; +} + +/* Get the locale for an 'Accept-Language' request header field element. */ +static locale_t +get_locale_matching_element (char *element_start, char *element_end) +{ + char *p; + + /* Ignore the element part that starts with a semicolon. */ + for (p = element_start; p < element_end && *p != ';'; p++) + ; + element_end = p; + + /* Trim the element. */ + while (element_start < element_end && element_end[-1] == ' ') + element_end--; + while (element_start < element_end && element_start[0] == ' ') + element_start++; + if (element_start == element_end) + return NULL; + + char saved = *element_end; + *element_end = '\0'; + locale_t result = get_locale_matching_language (element_start); + *element_end = saved; + + return result; +} + +/* Get the locale for an 'Accept-Language' request header field. */ +static locale_t +get_locale_matching_field (char *field_start, char *field_end) +{ + /* The field's value is a comma-separated list of "lang [; q=...]" elements. + Each lang is of the form ll-CC, not a BCP 47 string. + Therefore, for Chinese, expect zh-CN, zh-TW, etc. See + */ + char *element_start = field_start; + for (;;) + { + char *p; + + for (p = element_start; p < field_end && *p != ','; p++) + ; + char *element_end = p; + + locale_t locale_for_element = + get_locale_matching_element (element_start, element_end); + /* If the locale is not supported on this system, skip this element and + continue with the next one. */ + if (locale_for_element != NULL) + return locale_for_element; + + if (element_end == field_end) + break; + element_start = element_end + 1; + } + return NULL; +} + +/* Extract the desired locale from the value of the 'Accept-Language' + request header field. */ +static locale_t +get_locale_from_header (char *header_start, char *header_end) +{ + char *line_start = header_start; + while (line_start < header_end) + { + char *line_end = strchr (line_start, '\r'); + if (line_end == NULL) + abort (); + if (line_end - line_start >= 16 + && strncasecmp (line_start, "Accept-Language:", 16) == 0) + { + char *field_start = line_start + 16; + char *field_end = line_end; + return get_locale_matching_field (field_start, field_end); + } + line_start = line_end + 2; + } + return NULL; +} + + +/* This function defines what each thread does. */ + +static void * +server_thread (void *arg) +{ + int server_socket = *(int const *) arg; + enum { BUFFER_SIZE = 4096 }; + + for (;;) + { + /* Accept an incoming connection. */ + struct sockaddr_storage addr; + socklen_t addrlen = sizeof (addr); + int connected_socket = + accept (server_socket, (struct sockaddr *) &addr, &addrlen); + if (connected_socket >= 0) + { + /* Receive the initial part of an HTTP request. */ + char request[BUFFER_SIZE + 1]; + int req_len = recv (connected_socket, request, BUFFER_SIZE, 0); + if (req_len >= 0) + { + /* Determine the extent of the HTTP request header. + We are not interested in the message body. */ + char *header_start; + char *header_end; + { + request[req_len] = '\0'; + char *line_start = request; + char *line_end = strchr (line_start, '\r'); + if (line_end != NULL && line_end[1] == '\n') + { + header_start = line_start = line_end + 2; + for (;;) + { + line_end = strchr (line_start, '\r'); + if (!(line_end != NULL && line_end[1] == '\n' + && line_end > line_start)) + /* An empty line ends the header and starts the body. */ + break; + line_start = line_end + 2; + } + header_end = line_start; + } + else + header_start = header_end = request; + } + /* Determine the locale. */ + locale_t locale = + get_locale_from_header (header_start, header_end); + /* Set the locale on this thread. + If locale == NULL, we use the thread's default locale, which + is the global locale, which is "C". */ + if (locale != NULL) + uselocale (locale); + + /* Get the localized HTTP response body. */ + char *response_body; + /* Some HTML could be added here. */ + if (asprintf (&response_body, "%s\n", gettext ("Hello, world!")) >= 0) + { + /* Writing to the connected_socket via send() is the same as + via write(). So, we can use dprintf(). Alternatively, + one could use fdopen() and fprintf(). */ + dprintf (connected_socket, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "Content-Length: %lu\r\n" + "Connection: close\r\n" + "\r\n" + "%s", + (unsigned long) strlen (response_body), + response_body); + free (response_body); + } + + /* Restore the previous locale. */ + if (locale != NULL) + uselocale (LC_GLOBAL_LOCALE); + } + close (connected_socket); + + /* Enable this to ensure that different threads get actually used. */ + if (0) + { + struct timespec duration = { .tv_sec = 60, .tv_nsec = 0 }; + nanosleep (&duration, NULL); + } + } + } + return NULL; +} + + +/* Main program. */ + +#define PORT 8080 + +/* The IPv4 server socket. */ +int server_socket4; +/* The IPv6 server socket. */ +int server_socket6; + +/* Number of threads per socket. */ +#define NUM_THREADS 10 + +int +main () +{ + textdomain ("hello-c-http"); + bindtextdomain ("hello-c-http", LOCALEDIR); + + /* Initialize all_languages. */ + unsigned int i; + for (i = 0; i < sizeof (all_languages) / sizeof (all_languages[0]); i++) + all_languages[i].locale = + newlocale (LC_ALL_MASK, all_languages[i].locale_name, NULL); + + /* Initialize an IPv4 server socket. */ + { + server_socket4 = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (server_socket4 < 0) + return 1; + + /* Avoid an EADDRINUSE error in the next bind() call. */ + unsigned int flag = 1; + setsockopt (server_socket4, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag)); + + struct sockaddr_in server_addr; + memset (&server_addr, 0, sizeof (server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons (PORT); + if (bind (server_socket4, (struct sockaddr *) &server_addr, sizeof (server_addr)) < 0) + return 2; + + if (listen (server_socket4, 10) < 0) + return 3; + } + + /* Initialize an IPv6 server socket. */ + { + server_socket6 = socket (PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (server_socket6 >= 0) + { + /* Avoid an EADDRINUSE error in the next bind() call. */ + unsigned int flag = 1; + setsockopt (server_socket6, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag)); + /* We don't want dual-socket support here. */ + setsockopt (server_socket6, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof (flag)); + + struct sockaddr_in6 server_addr; + memset (&server_addr, 0, sizeof (server_addr)); + server_addr.sin6_family = AF_INET6; + server_addr.sin6_addr = in6addr_any; + server_addr.sin6_port = htons (PORT); + if (bind (server_socket6, (struct sockaddr *) &server_addr, sizeof (server_addr)) < 0) + return 4; + + if (listen (server_socket6, 10) < 0) + return 5; + } + } + + printf ("Server is listening on port %d...\n", PORT); + + pthread_t thread; + for (i = 0; i < NUM_THREADS; i++) + { + if (pthread_create (&thread, NULL, server_thread, &server_socket4) < 0) + return 6; + } + if (server_socket6 >= 0) + for (i = 0; i < NUM_THREADS; i++) + { + if (pthread_create (&thread, NULL, server_thread, &server_socket6) < 0) + return 6; + } + + /* Wait forever. */ + pthread_join (thread, NULL); +} diff --git a/gettext-tools/examples/hello-c-http/m4/Makefile.am b/gettext-tools/examples/hello-c-http/m4/Makefile.am new file mode 100644 index 000000000..7d516f09f --- /dev/null +++ b/gettext-tools/examples/hello-c-http/m4/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = \ + gettext.m4 build-to-host.m4 host-cpu-c-abi.m4 \ + iconv.m4 intlmacosx.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 \ + nls.m4 po.m4 progtest.m4 diff --git a/gettext-tools/examples/hello-c-http/po/LINGUAS b/gettext-tools/examples/hello-c-http/po/LINGUAS new file mode 100644 index 000000000..dc4a82afb --- /dev/null +++ b/gettext-tools/examples/hello-c-http/po/LINGUAS @@ -0,0 +1,5 @@ +# Example for use of GNU gettext. +# This file is in the public domain. +# +# Set of available languages. +af ast bg ca cs da de el eo es fi fr ga gl hr hu id it ja ka ky lv ms mt nb nl nn pl pt pt_BR ro ru sk sl sq sr sv ta tr uk vi zh_CN zh_HK zh_TW diff --git a/gettext-tools/examples/hello-c-http/po/Makevars b/gettext-tools/examples/hello-c-http/po/Makevars new file mode 100644 index 000000000..16af4d267 --- /dev/null +++ b/gettext-tools/examples/hello-c-http/po/Makevars @@ -0,0 +1,83 @@ +# Makefile variables for PO directory in any package using GNU gettext. +# +# Copyright (C) 2003-2019 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation gives +# unlimited permission to use, copy, distribute, and modify it. + +# Usually the message domain is the same as the package name. +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory. +subdir = po +top_builddir = .. + +# These options get passed to xgettext. +XGETTEXT_OPTIONS = \ + --keyword=_ --flag=_:1:pass-c-format \ + --keyword=N_ --flag=N_:1:pass-c-format + +# This is the copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding +# package. (Note that the msgstr strings, extracted from the package's +# sources, belong to the copyright holder of the package.) Translators are +# expected to transfer the copyright for their translations to this person +# or entity, or to disclaim their copyright. The empty string stands for +# the public domain; in this case the translators are expected to disclaim +# their copyright. +COPYRIGHT_HOLDER = Yoyodyne, Inc. + +# This tells whether or not to prepend "GNU " prefix to the package +# name that gets inserted into the header of the $(DOMAIN).pot file. +# Possible values are "yes", "no", or empty. If it is empty, try to +# detect it automatically by scanning the files in $(top_srcdir) for +# "GNU packagename" string. +PACKAGE_GNU = no + +# This is the email address or URL to which the translators shall report +# bugs in the untranslated strings: +# - Strings which are not entire sentences, see the maintainer guidelines +# in the GNU gettext documentation, section 'Preparing Strings'. +# - Strings which use unclear terms or require additional context to be +# understood. +# - Strings which make invalid assumptions about notation of date, time or +# money. +# - Pluralisation problems. +# - Incorrect English spelling. +# - Incorrect formatting. +# It can be your email address, or a mailing list address where translators +# can write to without being subscribed, or the URL of a web page through +# which the translators can contact you. +MSGID_BUGS_ADDRESS = bug-gettext@gnu.org + +# This is the list of locale categories, beyond LC_MESSAGES, for which the +# message catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = + +# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt' +# context. Possible values are "yes" and "no". Set this to yes if the +# package uses functions taking also a message context, like pgettext(), or +# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument. +USE_MSGCTXT = no + +# These options get passed to msgmerge. +# Useful options are in particular: +# --previous to keep previous msgids of translated messages +MSGMERGE_OPTIONS = + +# These options get passed to msginit. +# If you want to disable line wrapping when writing PO files, add +# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and +# MSGINIT_OPTIONS. +MSGINIT_OPTIONS = + +# This tells whether or not to regenerate a PO file when $(DOMAIN).pot +# has changed. Possible values are "yes" and "no". Set this to no if +# the POT file is checked in the repository and the version control +# program ignores timestamps. +PO_DEPENDS_ON_POT = yes + +# This tells whether or not to forcibly update $(DOMAIN).pot and +# regenerate PO files on "make dist". Possible values are "yes" and +# "no". Set this to no if the POT file and PO files are maintained +# externally. +DIST_DEPENDS_ON_UPDATE_PO = yes diff --git a/gettext-tools/examples/hello-c-http/po/POTFILES.in b/gettext-tools/examples/hello-c-http/po/POTFILES.in new file mode 100644 index 000000000..8215af41e --- /dev/null +++ b/gettext-tools/examples/hello-c-http/po/POTFILES.in @@ -0,0 +1,5 @@ +# Example for use of GNU gettext. +# This file is in the public domain. +# +# List of files which contain translatable strings. +hello-server.c diff --git a/gettext-tools/examples/po/Makefile.am b/gettext-tools/examples/po/Makefile.am index 9901db39e..992ff0c2a 100644 --- a/gettext-tools/examples/po/Makefile.am +++ b/gettext-tools/examples/po/Makefile.am @@ -29,6 +29,7 @@ POTFILES = \ hello-c-gnome3/hello2.desktop.in.in \ hello-c-gnome3/hello2.ui \ hello-c-gnome3/hello2.gschema.xml \ + hello-c-http/hello-server.c \ hello-c++/hello.cc \ hello-c++20/hello.cc \ hello-c++-qt/hello.cc \ @@ -75,6 +76,7 @@ SMALLPOTS = \ hello-c.pot \ hello-c-gnome2.pot \ hello-c-gnome3.pot \ + hello-c-http.pot \ hello-c++.pot \ hello-c++20.pot \ hello-c++-qt.pot \ @@ -259,6 +261,9 @@ hello-c-gnome2.pot : $(POTFILES_DEPS) hello-c-gnome3.pot : $(POTFILES_DEPS) $(USE_BUILT_PROGS) $(SHELL) '$(srcdir)/xsmallpot.sh' '$(srcdir)' hello-c-gnome3 +hello-c-http.pot : $(POTFILES_DEPS) + $(USE_BUILT_PROGS) $(SHELL) '$(srcdir)/xsmallpot.sh' '$(srcdir)' hello-c-http + hello-c++.pot : $(POTFILES_DEPS) $(USE_BUILT_PROGS) $(SHELL) '$(srcdir)/xsmallpot.sh' '$(srcdir)' hello-c++ @@ -431,6 +436,7 @@ SMALLPOFILES_FOR_lang = \ $(srcdir)/../hello-c/po/$$lang.po \ $(srcdir)/../hello-c-gnome2/po/$$lang.po \ $(srcdir)/../hello-c-gnome3/po/$$lang.po \ + $(srcdir)/../hello-c-http/po/$$lang.po \ $(srcdir)/../hello-c++/po/$$lang.po \ $(srcdir)/../hello-c++20/po/$$lang.po \ $(srcdir)/../hello-c++-qt/po/$$lang.po \ @@ -472,6 +478,9 @@ $(srcdir)/../hello-c-gnome2/po/$(LL).po: $(srcdir)/hello-c-gnome2.pot $(srcdir)/ $(srcdir)/../hello-c-gnome3/po/$(LL).po: $(srcdir)/hello-c-gnome3.pot $(srcdir)/$(LL).po $(USE_BUILT_PROGS) cd $(srcdir) && $(SHELL) mmsmallpo.sh hello-c-gnome3 $(LL) +$(srcdir)/../hello-c-http/po/$(LL).po: $(srcdir)/hello-c-http.pot $(srcdir)/$(LL).po + $(USE_BUILT_PROGS) cd $(srcdir) && $(SHELL) mmsmallpo.sh hello-c-http $(LL) + $(srcdir)/../hello-c++/po/$(LL).po: $(srcdir)/hello-c++.pot $(srcdir)/$(LL).po $(USE_BUILT_PROGS) cd $(srcdir) && $(SHELL) mmsmallpo.sh hello-c++ $(LL)