//---------------------------------------------------------------------------- // (C) Copyright 1994 by Borland International, All Rights Reserved // // TLocaleString implementation - localized name support // // NOTE: This code must reside in the same module that the strings are defined // The cache, NativeLangId, HINSTANCE are managed on a per-module basis // TLocaleString::NativeLangId may be user-implemented to symbol langid // TLocaleString::Module may be reset from this to another resource DLL //---------------------------------------------------------------------------- #if !defined(_Windows) # define _Windows // pretend we are in windows to get the headers we need #endif #include #include //---------------------------------------------------------------------------- // Module global default values - except for TLocaleString::NativeLangId // TLangId TLocaleString::SystemDefaultLangId = TLocaleString::GetSystemLangId(); TLangId TLocaleString::UserDefaultLangId = TLocaleString::GetUserLangId(); HINSTANCE TLocaleString::Module = _hInstance; TLocaleString TLocaleString::Null = {""}; //---------------------------------------------------------------------------- // TLocaleCache definitions, private for implementation // #define AUTOLANG_CACHEDNEUT 0x02 // prefix indicates cache entry with neutral #define AUTOLANG_CACHEDLOAD 0x01 // prefix indicates Neutral is not a string const TLangId InvalidLangId = 0xFFFF; struct TLocaleCache; struct TLocaleCacheBase; // // Static object to hold destructable pointer // struct TLocaleCacheList { TLocaleCacheList() : Next(0) {} ~TLocaleCacheList(); // releases cache entries TLocaleCache* Lookup(const char* name); // returns cache entry, 0 if failed TLocaleCacheBase* Next; // linked list of cached translations }; // // This base struct is used to cache failure to find language resource // struct TLocaleCacheBase { long Hash; // hashed original string, for duplicate elimination const char* Neutral; // original string, +1 if resource found and loaded TLocaleCacheBase* Next;// linked list of cached strings, for search, cleanup TLocaleCacheBase(const char* name, long hash); static TLocaleCacheList CacheList; }; // // Buffer follows this header, sized for maximum string + null term. // struct TLocaleCache : public TLocaleCacheBase { void* operator new(unsigned size, unsigned buflen); TLocaleCache(const char* name, long hash, HRSRC rscHdl, HGLOBAL resData); ~TLocaleCache() {} const char* Translate(TLangId lang); // (re)translate string TLangId ActLangId; // actual language ID of cached string TLangId ReqLangId; // requested language ID of cached string HRSRC ResHdl; // handle returned from ::FindResource() char Buf[1]; // first character is string type }; //---------------------------------------------------------------------------- // TLocaleCache implementation // TLocaleCacheBase::TLocaleCacheBase(const char* name, long hash) : Neutral(name), Hash(hash) { Next = CacheList.Next; CacheList.Next = this; } void* TLocaleCache::operator new(unsigned size, unsigned buflen) { return ::operator new(size+buflen); } TLocaleCache::TLocaleCache(const char* name, long hash, HRSRC resHdl, HGLOBAL resData) : TLocaleCacheBase(name, hash), ResHdl(resHdl) { ReqLangId = ActLangId = InvalidLangId; // indicate initializing state *(HGLOBAL*)(Buf+1) = resData; // store resource pointer temp in buffer } TLocaleCache* TLocaleCacheList::Lookup(const char* name) { const char* neut = name + 1; // skip over prefix flag char long hash = 0; const char* pc = name; while (*pc) hash = hash*2 ^ *pc++; for (TLocaleCacheBase* entry = Next; entry; entry = entry->Next) { if (hash == entry->Hash) { const char* pc = entry->Neutral; if (*pc != *neut) // Neutral points to prefix if lookup failed pc++; if (TLocaleString::CompareLang(pc,neut,TLocaleString::NativeLangId) != 0) return pc == entry->Neutral ? (TLocaleCache*)entry : 0; } } pc = name; if (*name != AUTOLANG_RCID) pc++; // '#' part of Id HRSRC resHdl = ::FindResource(TLocaleString::Module, pc, RT_LOCALIZATION); if (!resHdl) { new TLocaleCacheBase(name, hash); // add cache entry for failed lookup return 0; } HGLOBAL resData = ::LoadResource(TLocaleString::Module, resHdl); if (!resData) { return 0; // should throw exception on failure?!! } unsigned char far* pr = (unsigned char far*)::LockResource(resData); int maxLen = sizeof(HGLOBAL); // scan for longest string, including null unsigned char c = *pr; // check first byte of langid or neutral text if (c == 0) { // check for empty resource string ::FreeResource(resData); new TLocaleCacheBase(name, hash); // add failed cache entry if null or err return 0; } if (c >= ' ') // check for unprefixed neutral string first pr--; // cancel ++ in for loop initialization else pr++; // start to skip over 2-byte language id do { // loop to check for maximum string length unsigned char far* p = ++pr; // skip over id to start of translation while ((c=*pr++) >= ' ') ; // skip over translation string if ((int)(pr-p) > maxLen) // update maximum, including terminator maxLen = (int)(pr-p); } while(c); TLocaleCache* cache = new(maxLen) TLocaleCache(neut, hash, resHdl, resData); cache->Buf[0] = (*name == AUTOLANG_XLAT ? AUTOLANG_CACHEDNEUT : AUTOLANG_CACHEDLOAD); return cache; } const char* TLocaleCache::Translate(TLangId reqLang) { HGLOBAL resData; if (ReqLangId == InvalidLangId) { // if first time called after construction resData = *(HGLOBAL*)(Buf+1); ReqLangId = reqLang; } else { if (Buf[0]==AUTOLANG_CACHEDNEUT && TLocaleString::IsNativeLangId(reqLang)) return Neutral; if (reqLang == ActLangId) return Buf+1; if (reqLang == ReqLangId) { if (ActLangId != InvalidLangId) return Buf+1; else if (Buf[0] == AUTOLANG_CACHEDNEUT) return Neutral; else return 0; } if ((resData = ::LoadResource(TLocaleString::Module, ResHdl)) == 0) return Neutral; // should throw exception on failure?!! } unsigned char far* resBuf = (unsigned char far*)::LockResource(resData); unsigned char far* translation = 0; unsigned char far* pr = resBuf; int actLang = InvalidLangId; unsigned char c; int resLang; while ((c = *pr) != 0) { if (c > ' ') { // check for initial neutral string, used with CACHEDLOAD actLang = resLang = TLocaleString::NativeLangId; translation = pr; // lowest preference match } else { resLang = ((c - 1)<<10) | *++pr,pr++; } if (resLang == reqLang) { // exact match translation = pr; actLang = resLang; break; } if ((char)resLang == (char)reqLang) { // base language match if ((char)actLang != (char)reqLang || resLang == (reqLang & 0x00FF)) { translation = pr; actLang = resLang; } } for ( ; *pr >= ' '; ++pr) ; // skip over translation string till next Id } const char* retVal; if (translation) { while (*translation < ' ') // skip over multiple language IDs translation += 2; if (actLang != ActLangId) { // if same as in buffer, leave alone char* pc; for (pr = translation, pc = Buf + 1; *pr >= ' '; ) *pc++ = *pr++; *pc = 0; ActLangId = actLang; if (reqLang != ActLangId) ReqLangId = reqLang; } retVal = Buf+1; } else if (Buf[0] == AUTOLANG_CACHEDNEUT) { retVal = Neutral; } else { retVal = 0; } ::FreeResource(resData); return retVal; } TLocaleCacheList TLocaleCacheBase::CacheList; // declare module-global cache TLocaleCacheList::~TLocaleCacheList() { while (Next) { TLocaleCacheBase* p = Next; Next = Next->Next; delete p; } } //---------------------------------------------------------------------------- // TLocaleString implementation, except for static int CompareLang(s1,s2,lang) // const char* TLocaleString::Translate(TLangId reqLang) { if (!Private) // check for null string pointer return Private; if (reqLang == LangNeutral) reqLang = NativeLangId; else if (reqLang == LangSysDefault) reqLang = SystemDefaultLangId; else if (reqLang == LangUserDefault) reqLang = UserDefaultLangId; TLocaleCache* cache; switch (Private[0]) { default: // untranslatable string, no prefix return Private; case AUTOLANG_XLAT: // not yet translated if (IsNativeLangId(reqLang)) return Private+1; // resource name IS neutral or default name if ((cache = TLocaleCacheBase::CacheList.Lookup(Private)) == 0) return ++Private; // permanently bump pointer to make constant Private = cache->Buf; // point to buffer in cache return cache->Translate(reqLang); case AUTOLANG_LOAD: // named resource not accessed yet case AUTOLANG_RCID: // numeric resource not accessed yet if ((cache = TLocaleCacheBase::CacheList.Lookup(Private)) == 0) return (Private = 0); // permanently set pointer to null Private = cache->Buf; // point to buffer in cache return cache->Translate(reqLang); case AUTOLANG_CACHEDNEUT: // string in cache with neutral pointer case AUTOLANG_CACHEDLOAD: // string in cache with no neutral pointer cache = (TLocaleCache*)(Private+1) - 1; // backup to point to header return cache->Translate(reqLang); } } TLocaleString::operator const char*() { if (Private == 0) return 0; switch (Private[0]) { case AUTOLANG_XLAT: // not yet translated case AUTOLANG_CACHEDNEUT: // translated string in cache case AUTOLANG_CACHEDLOAD: // translated or neutral name in cache return Private+1; case AUTOLANG_RCID: // resource not accessed yet case AUTOLANG_LOAD: // resource not accessed yet return 0; default: // untranslatable string, no prefix return Private; } } int TLocaleString::Compare(const char far* str, TLangId lang) { return CompareLang(this->Translate(lang), str, lang); } int TLocaleString::IsNativeLangId(TLangId lang) { return lang == NativeLangId || lang == (NativeLangId & 0x00FF); } //---------------------------------------------------------------------------- // Registration parameter structures and formatting functions // const char* TRegList::Lookup(const char* key, TLangId lang) { for (TRegItem* regItem = Items; regItem->Key != 0; regItem++) { if (strcmp(regItem->Key, key) == 0) if (regItem->Value.Private) // current can't test Value directly return regItem->Value.Translate(lang); else return ""; } return 0; } TLocaleString& TRegList::LookupRef(const char* key) { for (TRegItem* regItem = Items; regItem->Key != 0; regItem++) { if (strcmp(regItem->Key, key) == 0) return regItem->Value; } return TLocaleString::Null; } //---------------------------------------------------------------------------- // // Maximum string length for REGFORMAT w/ string arg. String is clipped if too // long. // const int MaxFormatLen = 40; char* TRegItem::RegFormat(int f, int a, int t, int d, TRegFormatHeap& heap) { // sprintf into sized auto buffer // ints have a max of 11 digits: -2000000000. Add pad of 8 just in case // char temp[11+1+11+1+11+1+11+1 +8]; int len = sprintf(temp, "%d,%d,%d,%d", f,a,t,d); // Check for potential overflow // char* str = heap.Data + heap.Used; heap.Used += len + 1; if (heap.Used > heap.Size) return ""; // Copy into real static buffer & return it // return strcpy(str, temp); } char* TRegItem::RegFormat(const char* f, int a, int t, int d, TRegFormatHeap& heap) { // sprintf into sized auto buffer // char temp[MaxFormatLen+1+11+1+11+1+11+1 +8]; int len = sprintf(temp, "%.*s,%d,%d,%d", MaxFormatLen, (char far*)f,a,t,d); // Check for potential overflow // char* str = heap.Data + heap.Used; heap.Used += len + 1; if (heap.Used > heap.Size) return ""; // Copy into real static buffer & return it // return strcpy(str, temp); } char* TRegItem::RegFlags(long flags, TRegFormatHeap& heap) { // sprintf into sized auto buffer // char temp[11+1 +8]; int len = sprintf(temp, "%ld", flags); // Check for potential overflow // char* str = heap.Data + heap.Used; heap.Used += len + 1; if (heap.Used > heap.Size) return ""; // Copy into real static buffer & return it // return strcpy(str, temp); } char* TRegItem::RegVerbOpt(int mf, int sf, TRegFormatHeap& heap) { // sprintf into sized auto buffer // char temp[11+1+11+1 +8]; int len = sprintf(temp, "%d,%d", mf, sf); // Check for potential overflow // char* str = heap.Data + heap.Used; heap.Used += len + 1; if (heap.Used > heap.Size) return ""; // Copy into real static buffer & return it // return strcpy(str, temp); }