/*****************************************************************************/ /* lang.c Loads and processes the language files. NOTE TO LANGUAGE FILE AUTHORS ----------------------------- Messages can be used in all sorts of contexts, particularly inside string literal quotes - both single and double. It is therefore necessary to substitute the HTML entities ", ‘, ’, etc., for anything that might be misinterpreted as C-language or JavaScript code quotes. COPYRIGHT --------- Copyright (C) 2005-2023 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. VERSION HISTORY --------------- 27-JUN-2010 MGD bugfix; LangLoad() when WATCHing array boundary 01-OCT-2007 MGD LangOctalEntity() to octal-escape HTML entities for use in JavaScript alert() and confirm() strings 30-MAR-2006 MGD LangLoad() in conjunction with LANGDEF.H used to provide details on missing/extra entries in a language file 15-MAR-2006 MGD LangSame() modify comparison so that the message can contain HTML entities useful in some buttons (e.g. ) 11-MAR-2006 MGD LangLoad() convert unescaped newlines to spaces 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V7 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #pragma nomember_alignment /* standard C header files */ #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include /* application header file */ #include "soymail.h" #include "lang.h" #include "config.h" #define FI_LI __FILE__, __LINE__ /* global storage */ /* this include provides a global storage array of required message names */ #include "langdef.h" int LangMsgCount, LangTextLength; char *LangTextPtr; /* each has enough spare space to load extra messages to report file errors */ #define LANG_MSG_EXTRA 32 char *LangMsgNamePtr [LANG_MSG_TOTAL+LANG_MSG_EXTRA], *LangMsgValuePtr [LANG_MSG_TOTAL+LANG_MSG_EXTRA]; /* external storage */ extern BOOL Debug, WatchEnabled; extern CONFIG_DATA SoyMailConfig; /*****************************************************************************/ /* The user's optioned language name is passed as a parameter. Load the language file into memory as a series of newline-delimitted strings. Then leaving that in memory parse the message names and message text and point at the start of each from the respective message pointer array. If the optioned langauge is not found provide a status message and fall back to the site's default language (can be non-English). If the default language cannot be found fall back again to the mandatory language, English, and provide a status message. If that can't be found - give up and exit! */ void LangLoad (char *LangName) { BOOL WatchIsEnabled; int lmidx, ldidx, status, LangErrorCount; char *cptr, *sptr; char FileName [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LangLoad() |%s|\n", LangName); cptr = LangName; if (!cptr || !*cptr) cptr = LANG_MANDATORY; /* first the optioned language file (if any) then fall-back to English */ for (;;) { sprintf (FileName, "SOYMAIL_LANG:%s.TXT", cptr); if (WatchEnabled) WatchThis ("LANGUAGE source !AZ", FileName); status = ReadFileIntoMemory (FileName, &LangTextPtr, &LangTextLength); if (VMSok (status)) break; if (status != RMS$_FNF) ErrorExit (status, FI_LI); if (!strcmp (cptr, LANG_MANDATORY)) { CgiLibResponseError (FI_LI, status, "English (mandatory) language."); exit (SS$_NORMAL); } /* provide a language not found status message */ StatusMessage (FI_LI, 1, "Language "%s": %s.", cptr, SysGetMsg(status)); cptr = LANG_MANDATORY; } /* don't need all those parse messages now we have langdef.h */ WatchIsEnabled = WatchEnabled; WatchEnabled = FALSE; LangMsgCount = lmidx = 0; while (*LangTextPtr) { cptr = ConfigTextParse (&LangTextPtr, &LangMsgNamePtr[lmidx], &LangMsgValuePtr[lmidx]); if (!cptr) break; /* convert newlines to spaces unless escaped */ for (cptr = LangMsgValuePtr[lmidx]; *cptr; cptr++) { /* do not shuffle to eliminate backslashes unless necessary */ if (*(USHORTPTR)cptr == '\\\n') { /* ok, now it's necessary! */ for (sptr = cptr; *cptr; *sptr++ = *cptr++) if (*(USHORTPTR)cptr == '\\\n') cptr++; *sptr = '\0'; break; } if (*cptr == '\n') *cptr = ' '; } if (WatchIsEnabled && !LangMsgValuePtr[lmidx][0]) WatchThis ("LANGUAGE empty [!AZ]", LangMsgNamePtr[lmidx]); if (LangMsgCount < LANG_MSG_TOTAL+LANG_MSG_EXTRA) lmidx++; LangMsgCount++; } WatchEnabled = WatchIsEnabled; /* also perform the check and integrity report when watch is enabled */ if (!WatchEnabled && LangMsgCount == LANG_MSG_TOTAL) return; /**********************/ /* check message file */ /**********************/ if (WatchEnabled) { LangErrorCount = 0; /* display messages that should be there and aren't */ for (ldidx = 0; ldidx < LANG_MSG_TOTAL-1; ldidx++) { for (lmidx = 0; lmidx < LangMsgCount; lmidx++) if (strsame (LangMsgNamePtr[lmidx], LangDefList[ldidx], -1)) break; if (lmidx >= LangMsgCount) { LangErrorCount++; WatchThis ("LANGUAGE missing [!AZ]", LangDefList[ldidx]); } } /* display messages that shouldn't be there and are */ for (lmidx = 0; lmidx < LangMsgCount; lmidx++) { for (ldidx = 0; ldidx < LANG_MSG_TOTAL-1; ldidx++) if (strsame (LangMsgNamePtr[lmidx], LangDefList[ldidx], -1)) break; if (ldidx >= LANG_MSG_TOTAL) { LangErrorCount++; WatchThis ("LANGUAGE unknown [!AZ]", LangMsgNamePtr[lmidx]); } } if (WatchEnabled) WatchThis ("LANGUAGE !UL/!UL messages, !UL error!%S", LangMsgCount, LANG_MSG_TOTAL, LangErrorCount); } if (LangMsgCount != LANG_MSG_TOTAL) { CgiLibResponseError (FI_LI, SS$_BUGCHECK, "language file."); exit (SS$_NORMAL); } } /*****************************************************************************/ /* Return a pointer to the local language equivalent of the English language string parameter. */ char* LangFor (char *StringPtr) { static char NotFoundBuffer [64]; int idx; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LangFor() |%s|\n", StringPtr); for (idx = 0; idx < LangMsgCount; idx++) { if (!strsame (LangMsgNamePtr[idx], StringPtr, -1)) continue; if (Debug) fprintf (stdout, "|%s|\n", LangMsgValuePtr[idx]); return (LangMsgValuePtr[idx]); } zptr = (sptr = NotFoundBuffer) + sizeof(NotFoundBuffer)-2; *sptr++ = '?'; for (cptr = StringPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '?'; *sptr = '\0'; return (NotFoundBuffer); } /*****************************************************************************/ /* Compares the language string specified by 'MsgNamePtr' with the string specified by 'StringPtr'. As language strings can contain HTML entities, if the straight comparison fails then it's necessary to HTML-entify the string. Only entities permitted are <, >, &, ", and numeric enitites for characters greater than 127 decimal. If the comparison string is NULL the match fails. */ BOOL LangSame ( char *MsgNamePtr, char *StringPtr ) { int idx; char *cptr, *lptr, *sptr, *tptr, *zptr; char charBuf [2], dentBuf [256]; /*********/ /* begin */ /*********/ if (!StringPtr) return (FALSE); lptr = "-?-"; for (idx = 0; idx < LangMsgCount; idx++) { if (!strsame (LangMsgNamePtr[idx], MsgNamePtr, -1)) continue; lptr = LangMsgValuePtr[idx]; } if (!strcmp (StringPtr, lptr)) { if (WatchEnabled) WatchThis ("LANG TRUE \'!AZ\' \'!AZ\' \'!AZ\'", MsgNamePtr, StringPtr, lptr); return (TRUE); } if (!strchr (lptr, '&')) { /* there are no entified characters in the message */ if (WatchEnabled) WatchThis ("LANG FALSE \'!AZ\' \'!AZ\' \'!AZ\'", MsgNamePtr, StringPtr, lptr); return (FALSE); } zptr = (sptr = dentBuf) + sizeof(dentBuf)-1; for (cptr = lptr; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; CgiLibHtmlDeEntify (dentBuf); cptr = dentBuf; sptr = StringPtr; while (*cptr && *sptr) { /* cater for WIN32/DOS-style carriage-control */ if (*(USHORTPTR)sptr == '\r\n') sptr++; /* Opera 8.5 (at least) substitutes a space for in buttons! */ if (!(*cptr == '\n' && *sptr == ' ')) if (*cptr != *sptr) break; cptr++; sptr++; } if (!*cptr && !*sptr) { if (WatchEnabled) WatchThis ("LANG TRUE \'!AZ\' \'!AZ\' \'!AZ\' \'!AZ\'", MsgNamePtr, StringPtr, lptr, dentBuf); return (TRUE); } if (WatchEnabled) WatchThis ("LANG FALSE \'!AZ\' \'!AZ\' \'!AZ\' \'!AZ\'", MsgNamePtr, StringPtr, lptr, dentBuf); return (FALSE); } /*****************************************************************************/ /* There is a complication in the rendering of HTML entities when in JavaScript alerts and confirms. This function converts recognised entities into an octal equiavlent for use in JavaScript alerts. Returns a pointer to a dynamic string if escaping required. Apparently this is (perhaps a little) known phenomenon: Accents in alerts posted 25th November 2003 Ever needed to put accented characters such as é into a JavaScript alert? Its surprisingly problematic. Consider this simple function: function accentTest1() { alert('Ménage à trois.') } If you invoke the function, you get an alert as expected, but the HTML entities are not entified, they get them spelled out as typed in the code. To get around this problem, you have to use octal-encoded characters instead of HTML entities: alert('M\351nage \340 trois.') PJB has published a handy table for converting between octal, hex and HTML entities. http://clagnut.com/blog/261/ The 'handy table' can be found at: http://www.pjb.com.au/comp/diacritics.html And is reproduced here in case it disappears: US-Kbd Octal Hex HTML Details Alt-" \242 A2 ¢ ¢ cent sign Alt-# \243 A3 £ £ Pound sign (British) Alt-% \245 A5 ¥ ¥ Yen sign (Japanese) Alt-< \274 BC ¼ ¼ fraction one-quarter Alt-= \275 BD ½ ½ fraction one-half Alt-> \276 BE ¾ ¾ fraction three-quarters Alt-! \241 A1 ¡ ¡ upside-down exclamation mark Alt-+ \253 AB « « open chevron-style quotes Alt-; \273 BB » » close chevron-style quotes Alt-? \277 BF ¿ ¿ upside-down question mark Alt-@ \300 C0 À À capital A, grave accent Alt-A \301 C1 Á Á capital A, acute accent Alt-B \302 C2 Â Â capital A, circumflex accent Alt-C \303 C3 Ã Ã capital A, tilde Alt-D \304 C4 Ä Ä capital A, umlaut mark Alt-E \305 C5 Å Å capital A, ring Alt-F \306 C6 Æ Æ capital AE diphthong Alt-G \307 C7 Ç Ç capital C, cedilla Alt-H \310 C8 È È capital E, grave accent Alt-I \311 C9 É É capital E, acute accent Alt-J \312 CA Ê Ê capital E, circumflex accent Alt-K \313 CB Ë Ë capital E, umlaut mark Alt-L \314 CC Ì Ì capital I, grave accent Alt-M \315 CD Í Í capital I, acute accent Alt-N \316 CE Î Î capital I, circumflex accent Alt-O \317 CF Ï Ï capital I, umlaut mark Alt-P \320 D0 Ð Ð capital Eth, Icelandic Alt-Q \321 D1 Ñ Ñ capital N, tilde Alt-R \322 D2 Ò Ò capital O, grave accent Alt-S \323 D3 Ó Ó capital O, acute accent Alt-T \324 D4 Ô Ô capital O, circumflex accent Alt-U \325 D5 Õ Õ capital O, tilde Alt-V \326 D6 Ö Ö capital O, umlaut mark Alt-X \330 D8 Ø Ø capital O, slash Alt-Y \331 D9 Ù Ù capital U, grave accent Alt-Z \332 DA Ú Ú capital U, acute accent Alt-[ \333 DB Û Û capital U, circumflex accent Alt-\ \334 DC Ü Ü capital U, umlaut mark Alt-] \335 DD Ý Ý capital Y, acute accent Alt-^ \336 DE Þ Þ capital THORN, Icelandic Alt-_ \337 DF ß ß sz ligature, German Alt-` \340 E0 à à small a, grave accent Alt-a \341 E1 á á small a, acute accent Alt-b \342 E2 â â small a, circumflex accent Alt-c \343 E3 ã ã small a, tilde Alt-d \344 E4 ä ä small a, umlaut mark Alt-e \345 E5 å å small a, ring Alt-f \346 E6 æ æ small ae diphthong Alt-g \347 E7 ç ç small c, cedilla Alt-h \350 E8 è è small e, grave accent Alt-i \351 E9 é é small e, acute accent Alt-j \352 EA ê ê small e, circumflex accent Alt-k \353 EB ë ë small e, umlaut mark Alt-l \354 EC ì ì small i, grave accent Alt-m \355 ED í í small i, acute accent Alt-n \356 EE î î small i, circumflex accent Alt-o \357 EF ï ï small i, umlaut mark Alt-p \360 F0 ð ð small eth, Icelandic Alt-q \361 F1 ñ ñ small n, tilde Alt-r \362 F2 ò ò small o, grave accent Alt-s \363 F3 ó ó small o, acute accent Alt-t \364 F4 ô ô small o, circumflex accent Alt-u \365 F5 õ õ small o, tilde Alt-v \366 F6 ö ö small o, umlaut mark Alt-x \370 F8 ø ø small o, slash Alt-y \371 F9 ù ù small u, grave accent Alt-z \372 FA ú ú small u, acute accent Alt-{ \373 FB û û small u, circumflex accent Alt-| \374 FC ü ü small u, umlaut mark Alt-} \375 FD ý ý small y, acute accent Alt-~ \376 FE þ þ small thorn, Icelandic \377 FF ÿ ÿ small y, umlaut mark */ char* LangOctalEntity (char *StringPtr) { static struct OctalEntityStruct { char *octal, *entity; char eightbit; } OctalEntity [] = { { "\42", """, '\"' }, { "\47", "'", '\'' }, { "\46", "&", '&' }, { "\74", "<", '<' }, { "\76", ">", '>' }, { "\240", " ", ' ' }, { "\242", "¢", '¢' }, { "\243", "£", '£' }, { "\245", "¥", '¥' }, { "\274", "¼", '¼' }, { "\275", "½", '½' }, { "\276", "¾", '¾' }, { "\241", "¡", '¡' }, { "\253", "«", '«' }, { "\273", "»", '»' }, { "\277", "¿", '¿' }, { "\300", "À", 'À' }, { "\301", "Á", 'Á' }, { "\302", "Â", 'Â' }, { "\303", "Ã", 'Ã' }, { "\304", "Ä", 'Ä' }, { "\305", "Å", 'Å' }, { "\306", "Æ", 'Æ' }, { "\307", "Ç", 'Ç' }, { "\310", "È", 'È' }, { "\311", "É", 'É' }, { "\312", "Ê", 'Ê' }, { "\313", "Ë", 'Ë' }, { "\314", "Ì", 'Ì' }, { "\315", "Í", 'Í' }, { "\316", "Î", 'Î' }, { "\317", "Ï", 'Ï' }, { "\320", "Ð", 'Ð' }, { "\321", "Ñ", 'Ñ' }, { "\322", "Ò", 'Ò' }, { "\323", "Ó", 'Ó' }, { "\324", "Ô", 'Ô' }, { "\325", "Õ", 'Õ' }, { "\326", "Ö", 'Ö' }, { "\330", "Ø", 'Ø' }, { "\331", "Ù", 'Ù' }, { "\332", "Ú", 'Ú' }, { "\333", "Û", 'Û' }, { "\334", "Ü", 'Ü' }, { "\335", "Ý", 'Ý' }, { "\336", "Þ", 'Þ' }, { "\337", "ß", 'ß' }, { "\340", "à", 'à' }, { "\341", "á", 'á' }, { "\342", "â", 'â' }, { "\343", "ã", 'ã' }, { "\344", "ä", 'ä' }, { "\345", "å", 'å' }, { "\346", "æ", 'æ' }, { "\347", "ç", 'ç' }, { "\350", "è", 'è' }, { "\351", "é", 'é' }, { "\352", "ê", 'ê' }, { "\353", "ë", 'ë' }, { "\354", "ì", 'ì' }, { "\355", "í", 'í' }, { "\356", "î", 'î' }, { "\357", "ï", 'ï' }, { "\360", "ð", 'ð' }, { "\361", "ñ", 'ñ' }, { "\362", "ò", 'ò' }, { "\363", "ó", 'ó' }, { "\364", "ô", 'ô' }, { "\365", "õ", 'õ' }, { "\366", "ö", 'ö' }, { "\370", "ø", 'ø' }, { "\371", "ù", 'ù' }, { "\372", "ú", 'ú' }, { "\373", "û", 'û' }, { "\374", "ü", 'ü' }, { "\375", "ý", 'ý' }, { "\376", "þ", 'þ' }, { "\377", "ÿ", 'ÿ' }, { NULL, NULL, 0 } }; char *cptr, *optr, *sptr, *zptr; char OctalBuffer [256]; struct OctalEntityStruct *oeptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LangOctalEntity() |%s|\n", StringPtr); /* no need to go to all this trouble if nothing suggestive of an entity */ if (!strchr (StringPtr, '&')) return (StringPtr); zptr = (sptr = OctalBuffer) + sizeof(OctalBuffer)-1; cptr = StringPtr; while (*cptr && sptr < zptr) { if (*cptr == '&') { for (oeptr = (struct OctalEntityStruct*)&OctalEntity; oeptr->entity; oeptr++) if (!strncmp (cptr, oeptr->entity, strlen(oeptr->entity))) break; if (oeptr->entity) { for (optr = oeptr->octal; *optr && sptr < zptr; *sptr++ = *optr++); while (*cptr && *cptr != ';') cptr++; if (*cptr) cptr++; } else { while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == ';' && sptr < zptr) *sptr++ = *cptr++; } } else *sptr++ = *cptr++; } *sptr = '\0'; cptr = CgiLibVeeMemCalloc (sptr-OctalBuffer+1); if (!cptr) ErrorExit (vaxc$errno, FI_LI); strcpy (cptr, OctalBuffer); return (cptr); } /*****************************************************************************/