/*
  hello_locale.c

  Copyright 2000, Stephen J. Turnbull <stephen@xemacs.org>

  demonstrates use of locale support and gettext

  nb:  where "gettext documentation" is referred to, check both the
  documents distributed with gettext itself (the only source for auxiliary
  programs like xgettext and msgfmt) and the libc documentation on the
  gettext library entry points (more detailed and accurate)

  for this program works correctly by default, with the message catalogs
  installed below current directory (ie, in
    ./{LOCALE1,LOCALE2,...}/LC_MESSAGES/PLiP_hello.mo)

  alternatively, to find the catalogs in the standard Linux place,
    #define GETTEXT_DATA_ROOT "/usr/share/locale"
  and install them there as
    /usr/share/locale/{LOCALE1,LOCALE2,...}/LC_MESSAGES/PLiP_hello.mo

  finally, to use the system default locale data (you have to figure that
  out and install the catalogs there yourself) use
    #undef GETTEXT_DATA_ROOT
*/

/*
  CONFIGURATION DEFINES

  GETTEXT_DATA_ROOT points at the message localization database, ie the
    root of the LOCALE/CATEGORY/DOMAIN.mo hierarchy.  `"/usr/share/locale"'
    is normal on Linux systems (Debian with glibc 2.1.3, anyway),
    `getcwd(NULL,0)' (the default) locates it under the current directory,
    and `""' uses the system default.

  USE_YESNO_STR if defined (the default) gets the POSIX-defined yes/no
    strings for the locale.  Few locales define them, so this should be
    avoided (it's the default here for "educational purposes").
*/
#define GETTEXT_DATA_ROOT getcwd(NULL,0)
#define USE_YESNO_STR

/* get facilities needed ... */
#include <locale.h>
#include <langinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <regex.h>
#include <unistd.h>
/* ... whew! */

/*
  set up gettext facilities, including macros
  macros are conventional for gettext
  the N_ form is used to mark static data initializers
*/
#include <libintl.h>
#define _(String)  gettext (String)
#define N_(String) String

/* prototypes for functions defined */
void usage (char *);
void do_date ();
void do_hello ();

/* globals are evil, but I'm lazy */
char *default_locale;

/*
  driver program which sets locales and does some things correctly in them
  see struct menu_item menu[] for usage
*/
int main (int argc, char *argv[])
{

#ifdef GETTEXT_DATA_ROOT
  /* the storage allocated here is a memory leak in the default
     configuration */
  const char *gettext_data_root = GETTEXT_DATA_ROOT;
#endif

  /*
    initialize the locale functionality based on the environment
    see setlocale(3) for semantics
    see gettext docs for dangers of LC_ALL in some contexts
    do this FIRST, before parsing command line, to allow usage translation
  */
  default_locale = setlocale (LC_ALL, "");

  if (!default_locale)
  {
    fprintf (stderr,
	     /* actually handled internally by libc
		nb:  absolutely useless to gettextize this */
	     "%s: Arggh!  can't set default locale, get a new computer!\n",
	     argv[0]);
    /* normally I would limp along, but this is fatal in this example */
    exit(1);
  }

  /*
    initialize gettext:  bind default catalog

    the catalog is normally GETTEXT_DATA/LOCALE/LC_CATEGORY/TEXT_DOMAIN.mo,
    where GETTEXT_DATA is /usr/share/locale on Linux
          LOCALE is computed (from default_locale initialized above)
	  LC_CATEGORY = LC_MESSAGES (see gettext docs for exceptions)
	  TEXT_DOMAIN is set to "PLiP_hello" by calling textdomain() below

    nb: a separate library can't very well use a call to textdomain() to
    set a default domain---it will likely conflict with the user program
    libraries will normally use the dgettext() function whose first argument
    is the message domain
    of course a macro or inline function can be used for convenience
  */
  if (!textdomain ("PLiP_hello"))
  {
    fprintf (stderr,
	     /* nb:  absolutely useless to gettextize this */
	     "%s: Arggh!  can't set gettext domain, get more memory!\n",
	     argv[0]);
    /* this is fatal (textdomain() does no sanity check:
       must be due to (eg) ENOMEM */
    exit(1);
  }
#ifdef GETTEXT_DATA_ROOT
  if (!bindtextdomain ("PLiP_hello", gettext_data_root))
  {
    fprintf (stderr,
	     /* nb:  absolutely useless to gettextize this */
	     "%s: Arggh!  can't bind gettext domain, get more memory!\n",
	     argv[0]);
    /* this is fatal, must be due to (eg) ENOMEM */
    exit(1);
  }
  /*
    sanity check

    there has to be a better, more general way to do this.
    nb:  POSIX insists on standard usage in ISO 639 and 3166 for
    language and country codes.  That's OK, then.  GNU libc normalizes
    encodings, but that's not really good enough.
    Hmm ... if we insist on message catalogs in UTF-8, then convert on-
    the-fly if the terminal can't hack UTF-8, we win, don't we?

    note to translators:  this message should be translated to
    "If you were not expecting this language, check your LANGUAGE, LANG,
     LC_MESSAGES, and LC_ALL variables.  If they are set correctly, then
     the message catalog for your language has been misplaced."
  */
  puts (_("I hope you're happy with the default `POSIX' locale, because\
\nthat's what you've got!  No message catalog found for you."));
#endif

  puts (_("Successfully initialized I18N."));

  /* now check arguments */
  if (argc != 2 || argv[1][1] != '\0')
  {
    usage (argv[0]);
    exit (1);
  }

  switch (argv[1][0]) {
  case '1':
    do_date ();
    break;
  case '2':
    do_hello ();
    break;
  default:
    usage (argv[0]);
    exit (1);
  }

  putchar('\n');

  exit(0);
}

/*
  print the date
*/
void do_date ()
{
#define SIZE 256
  char date[SIZE];
  time_t now;
  struct tm *tmnow;

  time (&now);
  tmnow = localtime(&now);

  /* strftime(3) seems broken in GNU libc 2.1.3; time is correctly localized
     for the default localtime correction, but setting TZ or LANG gives GMT
     the manual indicates strftime should handle correction automatically */
  if (strftime (date, SIZE, "%c %Z", tmnow))
    printf (_("The time now is %s.\n"),
	    date);
  else
    printf ("do_date:  %s\n",
	    _("one of the dates formatted had zero length."));
}

void do_hello ()
{
  char buffer[SIZE];
  regex_t yre, nre;

  /* prep the regexps */
  if (regcomp (&yre, nl_langinfo(YESEXPR), REG_NOSUB)
      || regcomp (&nre, nl_langinfo(NOEXPR), REG_NOSUB))
  {
    fprintf (stderr, "do_hello: %s\n", _("regex compilation failed\n"));
    exit (1);
  }

  while (1)
  {
    /*
      prompt and read user input
      pretty boring, actually, since yesstr and nostr are deprecated and
      few locales provide them
      ar_SA, cs_CZ, sk_SK, th_TH, and tr_TR do, as does POSIX (but not in
      the source!) in GNU libc 2.1.3
    */
#ifdef USE_YESNO_STR
    printf (_("Would you like to see \"hello\" in some language [%s/%s]?  "),
	    nl_langinfo(YESSTR), nl_langinfo(NOSTR));
#else
    printf (_("Would you like to see \"hello\" in some language [yes/no]?  "));
#endif
    scanf ("%255s", buffer);

    /* parse answer */
    if (!regexec (&nre, buffer, 0, 0, 0))
    {
      /* negative answer matched */
      printf (_("Later, bye!\n"));
      exit (0);
    }
    else if (regexec (&yre, buffer, 0, 0, 0))
    {
      /* positive answer didn't match */
      printf (_("Answer the question!\n"));
      continue;			/* redundant */
    }
    else
    {
      /* positive answer matched */
      break;
    }
  }

  /* prompt and read user input */
  printf (_("Enter a POSIX-style locale:  "));
  scanf ("%255s", buffer);

  /* set locale */
  if (setlocale (LC_ALL, buffer))
    {
      /* Change language.  */
      setenv ("LANGUAGE", buffer, 1);

      /* Make change known.  */
      {
	extern int  _nl_msg_cat_cntr;
	++_nl_msg_cat_cntr;
      }
      printf (_("\"Hello\" in English is %s.\n"), "\"hello\"");
    }
  else
    printf (_("%s is not a real locale!  Not one I know, anyway.\n"),
	    buffer);

  /* clean up - obviously redundant here ...
     ... but it's the principle of the thing */
  setlocale (LC_ALL, default_locale);
}

/*
  purely compulsive...
*/
/* OPTIONS must be <= 9 */
#define OPTIONS 2

struct menu_item {
  char *tag;
  void (*function) ();
} menu[OPTIONS] = {
  /*
    these static strings cannot have calls to gettext()
    wrapped around them, so use noop variant
  */
  { N_("see the date in your locale"), do_date },
  { N_("see \"hello\" in various locales"), do_hello }
};

void usage (char *s)
{
  int i;

  printf (_("usage: %s {"), s);
  for (i = 1; i <= OPTIONS; ++i)
    printf ("%d%s", i, i == OPTIONS ? "}\n" : "|");

  for (i = 1; i <= OPTIONS; ++i)
    printf ("%d - %s\n", i, gettext (menu[i-1].tag));
}
