/* * to generate a template language pack, use: * build_sample_langpack --template *.rc *.cpp etc > file.langpack * */ #include #include #include #include #ifndef _WIN32 #define stricmp strcasecmp #endif #include "../wdltypes.h" #include "../assocarray.h" #include "../ptrlist.h" #include "../wdlstring.h" #include "../fnv64.h" static bool isblank(char c) { return c==' ' || c== '\t' || c=='\r' || c=='\n'; } static bool isblankorz(char c) { return !c || isblank(c); } WDL_StringKeyedArray section_descs; WDL_StringKeyedArray< WDL_PtrList * > translations; WDL_StringKeyedArray< WDL_StringKeyedArray * > translations_indexed; void gotString(const char *str, int len, const char *secname, bool isRC, const char *fn, int line) { WDL_PtrList *sec=translations.Get(secname); if (!sec) { sec=new WDL_PtrList; translations.Insert(secname,sec); } WDL_StringKeyedArray *sec2 = translations_indexed.Get(secname); if (!sec2) { sec2 = new WDL_StringKeyedArray(true); translations_indexed.Insert(secname,sec2); } if (len > (int)strlen(str)) { fprintf(stderr,"gotString got len>strlen(str) on %s:%d\n",fn,line); exit(1); } WDL_FastString buf; if (!isRC) { int st = 0; for (int x = 0; x < len; x ++) { if (!st) { if (str[x] == '\\' && str[x+1]) { buf.Append(str+x,2); x++; } else if (str[x] == '\"') st=1; else buf.Append(str+x,1); } else if (str[x] == '\"') st=0; else if (!isblank(str[x])) { fprintf(stderr,"gotString has junk between concatenated strings on %s:%d\n",fn,line); exit(1); } } } else { buf.Set(str,len); } if (sec2->Get(buf.Get())) return; //already in list sec2->Insert(buf.Get(),true); sec->Add(strdup(buf.Get())); } const char *g_last_file; int g_last_linecnt; int length_of_quoted_string(char *p, bool convertRCquotesToSlash) { int l=0; while (p[l]) { if (convertRCquotesToSlash && p[l] == '\"' && p[l+1] == '\"') p[l]='\\'; if (p[l] == '\"') { if (convertRCquotesToSlash) return l; // scan over whitespace to see if another string begins, concat them int l2 = l+1; while (isblank(p[l2])) l2++; if (p[l2] != '\"') return l; l = l2; } if (p[l] == '\\') { l++; } if (!p[l] || p[l] == '\r' || p[l] == '\n') break; l++; } fprintf(stderr,"ERROR: mismatched quotes in file %s:%d, check input!\n",g_last_file,g_last_linecnt); exit(1); return -1; } static int uint64cmpfunc(WDL_UINT64 *a, WDL_UINT64 *b) { if (*a < *b) return -1; if (*a > *b) return 1; return 0; } #define HACK_WILDCARD_ENTRY 2 static int isLocalizeCall(const char *p) { int rv = 0; if (!strncmp(p,"__LOCALIZE",10)) { p+=10; rv = 1; } else if (!strncmp(p,"ADD_WILDCARD_ENTRY",18)) { p+=18; rv = HACK_WILDCARD_ENTRY; } else return 0; if (*p == '_') while (*p == '_' || (*p >= 'A' && *p <= 'Z')||(*p>='0' && *p <='9')) p++; while (isblank(*p)) p++; return *p == '(' ? rv : 0; } WDL_UINT64 outputLine(const char *strv, int casemode) { WDL_UINT64 h = WDL_FNV64_IV; const char *p=strv; while (*p) { char c = *p++; if (c == '\\') { if (*p == '\\'||*p == '"' || *p == '\'') h=WDL_FNV64(h,(unsigned char *)p,1); else if (*p == 'n') h=WDL_FNV64(h,(unsigned char *)"\n",1); else if (*p == 'r') h=WDL_FNV64(h,(unsigned char *)"\r",1); else if (*p == 't') h=WDL_FNV64(h,(unsigned char *)"\t",1); else if (*p == '0') h=WDL_FNV64(h,(unsigned char *)"",1); else if (*p == 'x' && p[1] == 'e' && p[2] == '9') { h=WDL_FNV64(h,(unsigned char *)"\xe9",1); p+=2; } else { fprintf(stderr,"ERROR: unknown escape seq in '%s' at '%s'\n",strv,p); exit(1); } p++; } else h=WDL_FNV64(h,(unsigned char *)&c,1); } h=WDL_FNV64(h,(unsigned char *)"",1); printf("%08X%08X=",(int)(h>>32),(int)(h&0xffffffff)); int lc = 0; while (*strv) { int c = *strv++; if (lc == '%' || lc == '\\') { /* hacky*/ } else if (c == '\\' && strv[0] == 'x' && strv[1] == 'e' && strv[2] == '9') { strv+=3; c = 0xe9; } else if (casemode == 2) { switch (tolower(c)) { case 'o': c='0'; break; case 'i': c='1'; break; case 'e': c='3'; break; case 'a': c='4'; break; case 's': c='5'; break; } } else if (casemode==-1) c=tolower(c); else if (casemode==1) c=toupper(c); else if (casemode==4) { switch (c) { case 'E': c=0xc494; break; case 'A': c=0xc381; break; case 'I': c=0xc38f; break; case 'N': c=0xc391; break; case 'O': c=0xc395; break; case 'U': c=0xc39a; break; case 'B': c=0xc39f; break; case 'C': c=0xc486; break; case 'G': c=0xc4a0; break; case 'e': c=0xc497; break; case 'a': c=0xc3a4; break; case 'i': c=0xc4ad; break; case 'n': c=0xc584; break; case 'o': c=0xc58d; break; case 'u': c=0xc5af; break; case 'b': c=0xc39e; break; case 'c': c=0xc48d; break; case 'g': c=0xc49f; break; } if (c >= 256) { printf("%c",(c>>8)); c&=0xff; } } printf("%c",c); if (lc == '%' && (c == '.' || c=='l' || (c>='0' && c<='9'))) { // ignore .xyz and l between format spec (hacky) } else if (lc == '%' && c == '%') lc = 0; else if (lc == '\\' && c == '\\') lc = 0; else lc = c; } printf("\n"); return h; } WDL_StringKeyedArray g_resdefs; const char *getResourceDefinesFromHeader(const char *fn) { g_resdefs.DeleteAll(); FILE *fp=fopen(fn,"rb"); if (!fp) return "error opening header"; for (;;) { char buf[8192]; g_last_linecnt++; if (!fgets(buf,sizeof(buf),fp)) break; char *p = buf; while (*p) p++; while (p>buf && (p[-1] == '\r'|| p[-1] == '\n' || p[-1] == ' ')) p--; *p=0; if (!strncmp(buf,"#define",7)) { p=buf; while (*p && *p != ' '&& *p != '\t') p++; while (*p == ' ' || *p == '\t') p++; char *n1 = p; while (*p && *p != ' '&& *p != '\t') p++; if (*p) *p++=0; while (*p == ' ' || *p == '\t') p++; int a = atoi(p); if (a && *n1) { g_resdefs.Insert(n1,a); } } } fclose(fp); return NULL; } void processRCfile(FILE *fp, const char *dirprefix, const char *filename) { char sname[512]; sname[0]=0; int depth=0; for (;;) { char buf[8192]; g_last_linecnt++; if (!fgets(buf,sizeof(buf),fp)) break; char *p = buf; while (*p) p++; while (p>buf && (p[-1] == '\r'|| p[-1] == '\n' || p[-1] == ' ')) p--; *p=0; p=buf; if (sname[0]) while (*p == ' ' || *p == '\t') p++; char *first_tok = p; if (!strncmp(first_tok,"CLASS",5) && isblank(first_tok[5]) && first_tok[6]=='\"') continue; while (*p && *p != '\t' && *p != ' ') p++; if (*p) *p++=0; while (*p == '\t' || *p == ' ') p++; char *second_tok = p; if ((!strncmp(second_tok,"DIALOG",6) && isblankorz(second_tok[6])) || (!strncmp(second_tok,"DIALOGEX",8) && isblankorz(second_tok[8]))|| (!strncmp(second_tok,"MENU",4) && isblankorz(second_tok[4]))) { if (sname[0]) { fprintf(stderr,"got %s inside a block\n",second_tok); exit(1); } int sec = g_resdefs.Get(first_tok); if (!sec) { fprintf(stderr, "unknown dialog %s\n",first_tok); exit(1); } sprintf(sname,"%s%s%s_%d",dirprefix?dirprefix:"",dirprefix?"_":"",second_tok[0] == 'M' ? "MENU" : "DLG",sec); section_descs.Insert(sname,strdup(first_tok)); } else if (sname[0] && *second_tok && *first_tok) { if (*second_tok == '"') { int l = length_of_quoted_string(second_tok+1,true); if (l>0) { gotString(second_tok+1,l,sname, true, filename, g_last_linecnt); // OSX menu support: store a 2nd string w/o \tshortcuts, strip '&' too // note: relies on length_of_quoted_string() pre-conversion above if (depth && strstr(sname, "MENU_")) { int j=0; char* m=second_tok+1; for(;;) { if (!*m || (*m == '\\' && *(m+1)=='t') || (*m=='\"' && *(m-1)!='\\')) { buf[j]=0; break; } if (*m != '&') buf[j++] = *m; m++; } if (j!=l) gotString(buf,j,sname,true, filename, g_last_linecnt); } } } } else if (!strcmp(first_tok,"BEGIN")) { depth++; } else if (!strcmp(first_tok,"END")) { depth--; if (depth<0) { fprintf(stderr,"extra END\n"); exit(1); } if (!depth) sname[0]=0; } } if (depth!=0) { fprintf(stderr,"missing some ENDs at end of rc file\n"); exit(1); } } void processCPPfile(FILE *fp, const char *filename) { char clocsec[512]; clocsec[0]=0; WDL_FastString fs; for (;;) { char buf[8192]; if (!fgets(buf,sizeof(buf),fp)) break; fs.Append(buf); } char *p = (char*)fs.Get(); char *comment_state = NULL; g_last_linecnt++; while (*p) { if (!strncmp(p,"//",2)) { comment_state = p; p+=2; } else if (*p == '\n') { g_last_linecnt++; comment_state = NULL; p++; } else if (!comment_state) { int hm; if (*p == '\\') { p++; if (*p) p++; } else if (*p == '\'') { p++; if (*p == '"') p++; } else if (*p == '"') { int l = length_of_quoted_string(p+1,false); if (clocsec[0]) { gotString(p+1,l,clocsec,false, filename, g_last_linecnt); } p += l+2; } else if ((p==(char*)fs.Get() || (!isalnum(p[-1]) && p[-1] != '_')) && (hm=isLocalizeCall(p))) { while (*p != '(') p++; p++; while (isblank(*p)) p++; if (*p++ != '"') { fprintf(stderr,"Error: missing \" on %s:%d\n",filename,g_last_linecnt); exit(1); } int l = length_of_quoted_string(p,false); char *sp = p; p+=l+1; while (isblank(*p)) p++; if (*p++ != ',') { fprintf(stderr,"Error: missing , on %s:%d\n",filename,g_last_linecnt); exit(1); } while (isblank(*p)) p++; if (*p++ != '"') { fprintf(stderr,"Error: missing second \" on %s:%d\n",filename,g_last_linecnt); exit(1); } int l2 = length_of_quoted_string(p,false); if (hm == HACK_WILDCARD_ENTRY) { gotString(p,l2,"render_wildcard",false, filename, g_last_linecnt); p += l2; } else { char sec[512]; memcpy(sec,p,l2); sec[l2]=0; p+=l2; gotString(sp,l,sec,false, filename, g_last_linecnt); } p++; } else p++; } else if (p > comment_state && p < comment_state + 4) { if (!strncmp(p,"!WANT_LOCALIZE_STRINGS_BEGIN:",29)) { p += 29; if (clocsec[0]) { fprintf(stderr,"Error: !WANT_LOCALIZE_STRINGS_BEGIN: before WANT_LOCALIZE_STRINGS_END on %s:%d\n",filename,g_last_linecnt); exit(1); } int a = 0; while (*p && !isblank(*p) && a < sizeof(clocsec)) clocsec[a++] = *p++; if (a >= sizeof(clocsec)) { fprintf(stderr,"Error: !WANT_LOCALIZE_STRINGS_BEGIN: too long on %s:%d\n",filename,g_last_linecnt); exit(1); } clocsec[a]=0; } else { if (!strncmp(p,"!WANT_LOCALIZE_STRINGS_END",26)) { if (!clocsec[0]) { fprintf(stderr,"Error: mismatched !WANT_LOCALIZE_STRINGS_END on %s:%d\n",filename,g_last_linecnt); exit(1); } clocsec[0]=0; } p++; } } else p++; } if (clocsec[0]) { fprintf(stderr,"Error: missing !WANT_LOCALIZE_STRINGS_END at eof %s:%d\n",filename,g_last_linecnt); exit(1); } } int main(int argc, char **argv) { int x; int casemode=0; for (x=1;x3 && !stricmp(argv[x]+alen-3,".rc")) { WDL_String s(argv[x]); WDL_String dpre; char *p=s.Get(); while (*p) p++; while (p>=s.Get() && *p != '\\' && *p != '/') p--; *++p=0; if (p>s.Get()) { p-=2; while (p>=s.Get() && *p != '\\' && *p != '/') p--; dpre.Set(++p); // get dir name portion if (dpre.GetLength()) dpre.Get()[dpre.GetLength()-1]=0; } if (!strcmp(dpre.Get(),"jesusonic")) dpre.Set("jsfx"); s.Append("resource.h"); const char *err=getResourceDefinesFromHeader(s.Get()); if (err) { fprintf(stderr,"Error reading %s: %s\n",s.Get(),err); exit(1); } processRCfile(fp,dpre.Get()[0]?dpre.Get():NULL, argv[x]); } else { processCPPfile(fp,argv[x]); } fclose(fp); } if (casemode==4) printf("\xef\xbb\xbf"); printf("#NAME:%s\n", casemode==-1 ? "English (lower case, demo)" : casemode==1 ? "English (upper case, demo)" : casemode==2 ? "English (leet-speak, demo)" : casemode==4 ? "UTF-8 English test (demo)" : casemode==3 ? "Template (edit-me)" : "English (sample language pack)"); if (casemode==3) { printf("; NOTE: this is the best starting point for making a new langpack.\n" "; As you translate a string, remove the ; from the beginning of the\n" "; line. If the line begins with ;^, then it is an optional string,\n" "; and you should only modify that line if the definition in [common]\n" "; is not accurate for that context.\n" "; You can enlarge windows using 5CA1E00000000000=scale, for example:\n" "; [DLG_218] ; IDD_LOCKSETTINGS\n" "; 5CA1E00000000000=1.2\n" "; This makes the above dialog 1.2x wider than default.\n\n"); } WDL_StringKeyedArray common_found; { if (!translations_indexed.GetSize()) { fprintf(stderr,"no sections!\n"); exit(1); } else if (translations_indexed.GetSize() > 4096) { fprintf(stderr,"too many translation sections, check input or adjust code here\n"); exit(1); } fprintf(stderr,"%d sections\n",translations_indexed.GetSize()); int pos[4096]={0,}; printf("[common]\n"); WDL_FastString matchlist; WDL_AssocArray ids(uint64cmpfunc); int minpos = 0; for (;;) { int matchcnt=0; matchlist.Set(""); const char *str=NULL; for(x=minpos;x *l = translations_indexed.Enumerate(x,&secname); int sz=l->GetSize(); if (!str) { if (x>minpos) { memset(pos,0,sizeof(pos)); // start over minpos=x; } while (!str && pos[x]Enumerate(pos[x]++,&str); if (!*str || common_found.Get(str)) str=NULL; // skip if we've already analyzed this string } if (str) matchlist.Set(secname); } else { while (pos[x] < sz) { const char *tv=NULL; l->Enumerate(pos[x],&tv); int c = strcmp(tv,str); if (c>0) break; pos[x]++; if (!c) { matchlist.Append(", "); matchlist.Append(secname); matchcnt++; break; } } } } if (matchcnt>0) { common_found.Insert(str,true); //printf("; used by: %s\n",matchlist.Get()); if (casemode==3) printf(";"); WDL_UINT64 a = outputLine(str,casemode); if (ids.Get(a)) { fprintf(stderr,"duplicate hash for strings in common section, hope this is OK.. 64 bit hash fail!\n"); exit(1); } // printf("\n"); ids.Insert(a,true); } if (minpos == x-1) break; } printf("\n"); } for(x=0;x *p = translations.Enumerate(x,&nm); if (x) printf("\n"); char *secinfo = section_descs.Get(nm); printf("[%s]%s%s\n",nm,secinfo?" ; ":"", secinfo?secinfo:""); int a; int y; WDL_AssocArray ids(uint64cmpfunc); for (a=0;a<2;a++) { for (y=0;yGetSize();y++) { char *strv=p->Get(y); if (!*strv) continue; if ((common_found.Get(strv)?1:0) != a) continue; if (a) printf(";^"); else if (casemode==3) printf(";"); WDL_UINT64 a = outputLine(strv,casemode); if (ids.Get(a)) { fprintf(stderr,"duplicate hash for strings in section, hope this is OK.. 64 bit hash fail!\n"); exit(1); } ids.Insert(a,true); } } } return 0; }