[PATCH v14 0/3] MR10504: vbscript: Implement GetLocale / SetLocale and honor locale in Format*
Implements `GetLocale` and `SetLocale` (previously stubbed as `E_NOTIMPL`) and makes the locale-sensitive `Format*` built-ins actually respect the script's current locale. This mimics the jscript implementation. -- v14: vbscript: Pass script LCID to VariantChangeType in to_string helper. vbscript: Use script LCID in Format* functions. https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> --- dlls/vbscript/global.c | 43 ++++++++++++++++++++++++++++--- dlls/vbscript/tests/api.vbs | 51 +++++++++++++++++++++++++++++++++++++ dlls/vbscript/tests/run.c | 21 ++++++++++++++- dlls/vbscript/vbscript.c | 1 + dlls/vbscript/vbscript.h | 3 ++- 5 files changed, 113 insertions(+), 6 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index f1afcfade89..823601fb205 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -2229,8 +2229,43 @@ static HRESULT Global_Second(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, static HRESULT Global_SetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + LCID old_lcid = This->ctx->lcid; + LCID new_lcid; + HRESULT hres; + + TRACE("%s\n", args_cnt ? debugstr_variant(args) : "()"); + + if(!args_cnt || V_VT(args) == VT_EMPTY) { + new_lcid = This->ctx->host_lcid; + }else { + switch(V_VT(args)) { + case VT_NULL: + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + case VT_BSTR: { + int i; + /* Try numeric conversion first (e.g. "1033"), then locale name (e.g. "en-us"). */ + if(SUCCEEDED(to_int(args, &i))) + new_lcid = i; + else + new_lcid = LocaleNameToLCID(V_BSTR(args), 0); + break; + } + default: { + int i; + hres = to_int(args, &i); + if(FAILED(hres)) + return hres; + new_lcid = i; + break; + } + } + + if(!IsValidLocale(new_lcid, LCID_INSTALLED)) + return MAKE_VBSERROR(VBSE_LOCALE_SETTING_NOT_SUPPORTED); + } + + This->ctx->lcid = new_lcid; + return return_int(res, old_lcid); } static HRESULT Global_DateValue(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) @@ -3386,8 +3421,8 @@ static HRESULT Global_FormatPercent(BuiltinDisp *This, VARIANT *args, unsigned a static HRESULT Global_GetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + TRACE("() = %#lx\n", This->ctx->lcid); + return return_int(res, This->ctx->lcid); } static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) diff --git a/dlls/vbscript/tests/api.vbs b/dlls/vbscript/tests/api.vbs index 2a4f69f0f59..91b4bd10bb6 100644 --- a/dlls/vbscript/tests/api.vbs +++ b/dlls/vbscript/tests/api.vbs @@ -2794,4 +2794,55 @@ end sub call testFilterError() +' GetLocale/SetLocale tests +Dim origLocale +origLocale = GetLocale() +Call ok(getVT(GetLocale()) = "VT_I4", "getVT(GetLocale()) = " & getVT(GetLocale())) + +Dim prevLocale +prevLocale = SetLocale(1033) +Call ok(getVT(prevLocale) = "VT_I4*", "getVT(SetLocale result) = " & getVT(prevLocale)) +Call ok(prevLocale = origLocale, "SetLocale(1033) returned " & prevLocale & " expected " & origLocale) +Call ok(GetLocale() = 1033, "GetLocale() after SetLocale(1033) = " & GetLocale()) + +prevLocale = SetLocale(1031) +Call ok(prevLocale = 1033, "SetLocale(1031) returned " & prevLocale & " expected 1033") +Call ok(GetLocale() = 1031, "GetLocale() after SetLocale(1031) = " & GetLocale()) + +' SetLocale with string locale name +prevLocale = SetLocale("en-us") +Call ok(prevLocale = 1031, "SetLocale(""en-us"") returned " & prevLocale & " expected 1031") +Call ok(GetLocale() = 1033, "GetLocale() after SetLocale(""en-us"") = " & GetLocale()) + +' SetLocale with numeric string +prevLocale = SetLocale("1031") +Call ok(prevLocale = 1033, "SetLocale(""1031"") returned " & prevLocale & " expected 1033") +Call ok(GetLocale() = 1031, "GetLocale() after SetLocale(""1031"") = " & GetLocale()) + +' SetLocale() with no args resets to system default +SetLocale(1033) +prevLocale = SetLocale() +Call ok(prevLocale = 1033, "SetLocale() returned " & prevLocale & " expected 1033") + +sub testSetLocaleError() + on error resume next + + ' SetLocale(Null) should raise error 94 + call Err.clear() + call SetLocale(Null) + Call ok(Err.number = 94, "SetLocale(Null) Err.number = " & Err.number) + + ' SetLocale with invalid numeric LCID should raise error 447 + call Err.clear() + dim prev + prev = SetLocale(99999) + Call ok(Err.number = 447, "SetLocale(99999) Err.number = " & Err.number) +end sub + +call testSetLocaleError() + +' Restore original locale +SetLocale(origLocale) +Call ok(GetLocale() = origLocale, "GetLocale() after restore = " & GetLocale() & " expected " & origLocale) + Call reportSuccess() diff --git a/dlls/vbscript/tests/run.c b/dlls/vbscript/tests/run.c index f4b4829f902..668368bc5f3 100644 --- a/dlls/vbscript/tests/run.c +++ b/dlls/vbscript/tests/run.c @@ -1962,9 +1962,12 @@ static ULONG WINAPI ActiveScriptSite_Release(IActiveScriptSite *iface) return 1; } +/* 0 = use the user default; otherwise return this LCID to the engine. */ +static LCID site_lcid_override; + static HRESULT WINAPI ActiveScriptSite_GetLCID(IActiveScriptSite *iface, LCID *plcid) { - *plcid = GetUserDefaultLCID(); + *plcid = site_lcid_override ? site_lcid_override : GetUserDefaultLCID(); return S_OK; } @@ -3842,12 +3845,28 @@ static void run_from_res(const char *name) test_name = ""; } +static void test_setlocale_reset_uses_host_lcid(void) +{ + /* The engine takes the LCID returned by IActiveScriptSite::GetLCID as the + * baseline; SetLocale with no argument must restore it regardless of the + * user default. */ + site_lcid_override = 1036; /* fr-FR */ + parse_script_w(L"Call ok(GetLocale() = 1036, \"initial GetLocale = \" & GetLocale())\n" + L"SetLocale 1041\n" + L"Call ok(GetLocale() = 1041, \"after SetLocale 1041: \" & GetLocale())\n" + L"SetLocale\n" + L"Call ok(GetLocale() = 1036, \"after SetLocale reset: \" & GetLocale())\n"); + site_lcid_override = 0; +} + static void run_tests(void) { HRESULT hres; strict_dispid_check = TRUE; + test_setlocale_reset_uses_host_lcid(); + parse_script_w(L""); parse_script_w(L"' empty ;"); diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index c274e3a2f7f..ff28b3b17ef 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -735,6 +735,7 @@ static HRESULT WINAPI VBScript_SetScriptSite(IActiveScript *iface, IActiveScript IActiveScriptSite_GetLCID(This->ctx->site, &lcid); This->ctx->lcid = IsValidLocale(lcid, 0) ? lcid : GetUserDefaultLCID(); + This->ctx->host_lcid = This->ctx->lcid; GetLocaleInfoW(lcid, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, (WCHAR *)&This->ctx->codepage, sizeof(This->ctx->codepage)/sizeof(WCHAR)); if (!This->ctx->codepage) diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index e9eae6b5819..b9f238c3e3e 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -203,7 +203,8 @@ struct vbcaller { struct _script_ctx_t { IActiveScriptSite *site; - LCID lcid; + LCID lcid; /* current, mutable via SetLocale */ + LCID host_lcid; /* embedder-supplied baseline (IActiveScriptSite::GetLCID) */ UINT codepage; IInternetHostSecurityManager *secmgr; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> The SetLocale/GetLocale commit stored the new LCID on the script context but nothing read it: FormatNumber, FormatCurrency, FormatPercent and FormatDateTime all delegated to oleaut32 helpers that hardcode LOCALE_USER_DEFAULT, so scripts could not actually switch their formatting locale. Replicate the relevant oleaut32 number/currency format logic with the script LCID parameterized, and call GetDateFormatW / GetTimeFormatW directly for FormatDateTime (vbGeneralDate still falls back to VarFormatDateTime). The thread locale is intentionally left untouched: a new test in run.c verifies SetLocale does not leak into GetThreadLocale(). The script LCID is passed explicitly wherever formatting needs it. Expected Format* values in lang.vbs were captured with cscript on Windows (LCIDs 1031/1033/1041). --- dlls/vbscript/global.c | 292 ++++++++++++++++++++++++++--------- dlls/vbscript/tests/lang.vbs | 37 +++++ dlls/vbscript/tests/run.c | 10 ++ 3 files changed, 268 insertions(+), 71 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 823601fb205..64e5d72c8fc 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -3323,99 +3323,211 @@ static HRESULT Global_ScriptEngineBuildVersion(BuiltinDisp *This, VARIANT *arg, return return_int(res, VBSCRIPT_BUILD_VERSION); } -static HRESULT Global_FormatNumber(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +#define LOCNUM_INTO(lcid, type, field) GetLocaleInfoW((lcid), (type)|LOCALE_RETURN_NUMBER, \ + (LPWSTR)&(field), sizeof(field)/sizeof(WCHAR)) + +/* LCID_US gives the intermediate double-to-BSTR conversion a stable canonical + * form (period decimal, no grouping) that GetNumberFormatW / GetCurrencyFormatW + * will then reformat using the target locale. */ +#define LCID_EN_US MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) + +static HRESULT format_number_lcid(LCID lcid, VARIANT *var, int ndigits, int nleading, + int nparens, int ngrouping, BSTR *out) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + WCHAR buff[256], decimal[8], thousands[8]; + NUMBERFMTW fmt; + VARIANT v; HRESULT hres; - BSTR str; - int i; - TRACE("\n"); + *out = NULL; + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, var); + if(FAILED(hres)) return hres; + hres = VariantChangeTypeEx(&v, &v, LCID_EN_US, 0, VT_BSTR); + if(FAILED(hres)) return hres; - assert(1 <= args_cnt && args_cnt <= 5); + if(ndigits < 0) + LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); + else + fmt.NumDigits = ndigits; - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; + if(nleading == -2) + LOCNUM_INTO(lcid, LOCALE_ILZERO, fmt.LeadingZero); + else + fmt.LeadingZero = nleading == -1 ? 1 : 0; + + if(ngrouping == -2) { + WCHAR grouping[10] = {0}; + GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); + fmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; + }else { + fmt.Grouping = ngrouping == -1 ? 3 : 0; } - hres = VarFormatNumber(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + if(nparens == -2) + LOCNUM_INTO(lcid, LOCALE_INEGNUMBER, fmt.NegativeOrder); + else + fmt.NegativeOrder = nparens == -1 ? 0 : 1; - return return_bstr(res, str); + fmt.lpDecimalSep = decimal; + GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); + fmt.lpThousandSep = thousands; + GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); + + if(!GetNumberFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(V_BSTR(&v)); + return DISP_E_TYPEMISMATCH; + } + SysFreeString(V_BSTR(&v)); + + *out = SysAllocString(buff); + return *out ? S_OK : E_OUTOFMEMORY; } -static HRESULT Global_FormatCurrency(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +static HRESULT format_currency_lcid(LCID lcid, VARIANT *var, int ndigits, int nleading, + int nparens, int ngrouping, BSTR *out) +{ + WCHAR buff[256], decimal[8], thousands[4], currency[13]; + CURRENCYFMTW fmt; + VARIANT v; + HRESULT hres; + CY cy; + + *out = NULL; + if(V_VT(var) == VT_BSTR || V_VT(var) == (VT_BSTR|VT_BYREF)) { + hres = VarCyFromStr(V_ISBYREF(var) ? *V_BSTRREF(var) : V_BSTR(var), lcid, 0, &cy); + if(FAILED(hres)) return hres; + V_VT(&v) = VT_CY; + V_CY(&v) = cy; + }else { + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, var); + if(FAILED(hres)) return hres; + } + hres = VariantChangeTypeEx(&v, &v, lcid, 0, VT_BSTR); + if(FAILED(hres)) return hres; + + if(ndigits < 0) + LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); + else + fmt.NumDigits = ndigits; + + if(nleading == -2) + LOCNUM_INTO(lcid, LOCALE_ILZERO, fmt.LeadingZero); + else + fmt.LeadingZero = nleading == -1 ? 1 : 0; + + if(ngrouping == -2) { + WCHAR grouping[10] = {0}; + GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); + fmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; + }else { + fmt.Grouping = ngrouping == -1 ? 3 : 0; + } + + if(nparens == -2) + LOCNUM_INTO(lcid, LOCALE_INEGCURR, fmt.NegativeOrder); + else + fmt.NegativeOrder = nparens == -1 ? 0 : 1; + + LOCNUM_INTO(lcid, LOCALE_ICURRENCY, fmt.PositiveOrder); + fmt.lpDecimalSep = decimal; + GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); + fmt.lpThousandSep = thousands; + GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); + fmt.lpCurrencySymbol = currency; + GetLocaleInfoW(lcid, LOCALE_SCURRENCY, currency, ARRAY_SIZE(currency)); + + if(!GetCurrencyFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(V_BSTR(&v)); + return DISP_E_TYPEMISMATCH; + } + SysFreeString(V_BSTR(&v)); + + *out = SysAllocString(buff); + return *out ? S_OK : E_OUTOFMEMORY; +} + +static HRESULT parse_format_args(VARIANT *args, unsigned args_cnt, int *vals) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + HRESULT hres; + unsigned i; + for(i = 1; i < args_cnt; ++i) { + if(V_VT(args+i) == VT_ERROR) continue; + if(V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + hres = to_int(args+i, &vals[i-1]); + if(FAILED(hres)) return hres; + } + return S_OK; +} + +static HRESULT Global_FormatNumber(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +{ + int a[4] = {-1, -2, -2, -2}; HRESULT hres; BSTR str; - int i; TRACE("\n"); - assert(1 <= args_cnt && args_cnt <= 5); - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; - } + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + hres = format_number_lcid(This->ctx->lcid, args, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; + return return_bstr(res, str); +} + +static HRESULT Global_FormatCurrency(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +{ + int a[4] = {-1, -2, -2, -2}; + HRESULT hres; + BSTR str; - hres = VarFormatCurrency(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + TRACE("\n"); + assert(1 <= args_cnt && args_cnt <= 5); + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + hres = format_currency_lcid(This->ctx->lcid, args, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; return return_bstr(res, str); } static HRESULT Global_FormatPercent(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + int a[4] = {-1, -2, -2, -2}; + WCHAR buff[258]; + DWORD len; + VARIANT v; HRESULT hres; BSTR str; - int i; TRACE("\n"); - assert(1 <= args_cnt && args_cnt <= 5); - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; - } + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + + V_VT(&v) = VT_R8; + hres = to_double(args, &V_R8(&v)); + if(FAILED(hres)) return hres; + if(V_R8(&v) > (1e300 / 100.0)) return DISP_E_OVERFLOW; + V_R8(&v) *= 100.0; - hres = VarFormatPercent(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + hres = format_number_lcid(This->ctx->lcid, &v, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; + len = lstrlenW(str); + if(len && str[len-1] == ')') { + memcpy(buff, str, (len-1) * sizeof(WCHAR)); + lstrcpyW(buff + len - 1, L"%)"); + }else { + memcpy(buff, str, len * sizeof(WCHAR)); + lstrcpyW(buff + len, L"%"); + } + SysFreeString(str); + str = SysAllocString(buff); + if(!str) return E_OUTOFMEMORY; return return_bstr(res, str); } @@ -3428,6 +3540,10 @@ static HRESULT Global_GetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_ static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { int format = 0; + LCID lcid = This->ctx->lcid; + WCHAR buff[256]; + VARIANT v; + SYSTEMTIME st; HRESULT hres; BSTR str; @@ -3435,21 +3551,55 @@ static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned assert(1 <= args_cnt && args_cnt <= 2); - if (V_VT(args) == VT_NULL) + if(V_VT(args) == VT_NULL) return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); - if (args_cnt == 2) - { - if (V_VT(args+1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (V_VT(args+1) != VT_ERROR) - { - if (FAILED(hres = to_int(args+1, &format))) return hres; - } + if(args_cnt == 2 && V_VT(args+1) != VT_ERROR) { + if(V_VT(args+1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + hres = to_int(args+1, &format); + if(FAILED(hres)) return hres; + } + + /* vbGeneralDate (0) falls back to oleaut32 — the "show date if date, time if + * time, both otherwise" logic depends on the tokenizer; ctx->lcid does not + * yet propagate. */ + if(format == 0) { + hres = VarFormatDateTime(args, format, 0, &str); + if(FAILED(hres)) return hres; + return return_bstr(res, str); } - hres = VarFormatDateTime(args, format, 0, &str); - if (FAILED(hres)) return hres; + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, args); + if(FAILED(hres)) return hres; + hres = VariantChangeTypeEx(&v, &v, lcid, 0, VT_DATE); + if(FAILED(hres)) return hres; + if(!VariantTimeToSystemTime(V_DATE(&v), &st)) + return E_INVALIDARG; + + switch(format) { + case 1: /* vbLongDate */ + if(!GetDateFormatW(lcid, DATE_LONGDATE, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 2: /* vbShortDate */ + if(!GetDateFormatW(lcid, DATE_SHORTDATE, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 3: /* vbLongTime */ + if(!GetTimeFormatW(lcid, 0, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 4: /* vbShortTime */ + if(!GetTimeFormatW(lcid, TIME_NOSECONDS|TIME_FORCE24HOURFORMAT, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + default: + return E_INVALIDARG; + } + str = SysAllocString(buff); + if(!str) return E_OUTOFMEMORY; return return_bstr(res, str); } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index d139a2bf282..3b290a5a014 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -3584,4 +3584,41 @@ Call ok(Err.Number = 506, "New non-class variable: err.number = " & Err.Number) On Error GoTo 0 +' === GetLocale / SetLocale / locale-sensitive Format* === +Dim origLcid +origLcid = GetLocale() +Call ok(origLcid > 0, "GetLocale initial: " & origLcid) + +' SetLocale returns the previous LCID. +Call ok(SetLocale(1033) = origLcid, "SetLocale(1033) return value") +Call ok(GetLocale() = 1033, "GetLocale after SetLocale(1033): " & GetLocale()) + +' en-US formatting +Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber en-US: " & FormatNumber(1234567.89)) +Call ok(FormatCurrency(1234567.89) = "$1,234,567.89", "FormatCurrency en-US: " & FormatCurrency(1234567.89)) +Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent en-US: " & FormatPercent(0.1234)) +Call ok(FormatDateTime(DateSerial(2026,3,15), 1) = "Sunday, March 15, 2026", _ + "FormatDateTime en-US: " & FormatDateTime(DateSerial(2026,3,15), 1)) + +' de-DE: '.' thousands, ',' decimal +Call SetLocale(1031) +Call ok(GetLocale() = 1031, "GetLocale after SetLocale(1031)") +Call ok(FormatNumber(1234567.89) = "1.234.567,89", "FormatNumber de-DE: " & FormatNumber(1234567.89)) +Call ok(FormatPercent(0.1234) = "12,34%", "FormatPercent de-DE: " & FormatPercent(0.1234)) + +' ja-JP: Anglo number formatting +Call SetLocale(1041) +Call ok(GetLocale() = 1041, "GetLocale after SetLocale(1041)") +Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber ja-JP: " & FormatNumber(1234567.89)) +Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent ja-JP: " & FormatPercent(0.1234)) + +' CStr goes through VariantChangeType with LCID=0 (LOCALE_USER_DEFAULT). +' Plumbing ctx->lcid into that path is follow-up work. +Call SetLocale(1031) +Call todo_wine_ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) + +' Restore original locale. +Call SetLocale(origLcid) +Call ok(GetLocale() = origLcid, "restore: GetLocale = " & GetLocale()) + reportSuccess() diff --git a/dlls/vbscript/tests/run.c b/dlls/vbscript/tests/run.c index 668368bc5f3..b5f81d9e0f4 100644 --- a/dlls/vbscript/tests/run.c +++ b/dlls/vbscript/tests/run.c @@ -3859,6 +3859,15 @@ static void test_setlocale_reset_uses_host_lcid(void) site_lcid_override = 0; } +static void test_setlocale_does_not_touch_thread_locale(void) +{ + LCID thread_lcid_before = GetThreadLocale(); + parse_script_w(L"SetLocale 1036\n"); + ok(GetThreadLocale() == thread_lcid_before, + "SetLocale leaked to thread locale: before=%#lx after=%#lx\n", + thread_lcid_before, GetThreadLocale()); +} + static void run_tests(void) { HRESULT hres; @@ -3866,6 +3875,7 @@ static void run_tests(void) strict_dispid_check = TRUE; test_setlocale_reset_uses_host_lcid(); + test_setlocale_does_not_touch_thread_locale(); parse_script_w(L""); parse_script_w(L"' empty ;"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> CStr and other string coercions of numeric/date variants went through VariantChangeType(..., 0, VT_BSTR), so they always formatted with the user default locale regardless of SetLocale. Thread the script LCID through the to_string() helper (and the shared err_string_prop) so conversions honor the script-level locale. --- dlls/vbscript/global.c | 124 +++++++++++++++++------------------ dlls/vbscript/tests/lang.vbs | 7 +- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 64e5d72c8fc..2d478557629 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -453,13 +453,13 @@ static HRESULT to_float(VARIANT *v, float *ret) return S_OK; } -static HRESULT to_string(VARIANT *v, BSTR *ret) +static HRESULT to_string(LCID lcid, VARIANT *v, BSTR *ret) { VARIANT dst; HRESULT hres; V_VT(&dst) = VT_EMPTY; - hres = VariantChangeType(&dst, v, VARIANT_LOCALBOOL, VT_BSTR); + hres = VariantChangeTypeEx(&dst, v, lcid, VARIANT_LOCALBOOL, VT_BSTR); if(FAILED(hres)) return hres; @@ -836,7 +836,7 @@ static HRESULT Global_CStr(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V if(V_VT(arg) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1283,7 +1283,7 @@ static HRESULT Global_Len(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VA if(V_VT(arg) != VT_BSTR) { BSTR str; - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1313,7 +1313,7 @@ static HRESULT Global_Left(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1370,7 +1370,7 @@ static HRESULT Global_Right(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1438,7 +1438,7 @@ static HRESULT Global_Mid(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, V if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1496,11 +1496,11 @@ static HRESULT Global_StrComp(BuiltinDisp *This, VARIANT *args, unsigned args_cn else mode = 0; - hres = to_string(args, &left); + hres = to_string(This->ctx->lcid, args, &left); if(FAILED(hres)) return hres; - hres = to_string(args+1, &right); + hres = to_string(This->ctx->lcid, args+1, &right); if(FAILED(hres)) { SysFreeString(left); @@ -1526,7 +1526,7 @@ static HRESULT Global_LCase(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, return return_null(res); } - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1555,7 +1555,7 @@ static HRESULT Global_UCase(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, return return_null(res); } - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1584,7 +1584,7 @@ static HRESULT Global_LTrim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1611,7 +1611,7 @@ static HRESULT Global_RTrim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1638,7 +1638,7 @@ static HRESULT Global_Trim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1779,7 +1779,7 @@ static HRESULT Global_InStr(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, return return_null(res); if(V_VT(str1v) != VT_BSTR) { - hres = to_string(str1v, &str1); + hres = to_string(This->ctx->lcid, str1v, &str1); if(FAILED(hres)) return hres; } @@ -1787,7 +1787,7 @@ static HRESULT Global_InStr(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, str1 = V_BSTR(str1v); if(V_VT(str2v) != VT_BSTR) { - hres = to_string(str2v, &str2); + hres = to_string(This->ctx->lcid, str2v, &str2); if(FAILED(hres)){ if(V_VT(str1v) != VT_BSTR) SysFreeString(str1); @@ -1843,7 +1843,7 @@ static HRESULT Global_Asc(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VA str = V_BSTR(arg); break; default: - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1941,7 +1941,7 @@ static HRESULT Global_AscW(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V str = V_BSTR(arg); break; default: - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -2367,7 +2367,7 @@ static HRESULT Global_MsgBox(BuiltinDisp *This, VARIANT *args, unsigned args_cnt assert(1 <= args_cnt && args_cnt <= 5); - hres = to_string(args, &prompt); + hres = to_string(This->ctx->lcid, args, &prompt); if(FAILED(hres)) return hres; @@ -2375,7 +2375,7 @@ static HRESULT Global_MsgBox(BuiltinDisp *This, VARIANT *args, unsigned args_cnt hres = to_int(args+1, &type); if(SUCCEEDED(hres) && args_cnt > 2) - hres = to_string(args+2, &title); + hres = to_string(This->ctx->lcid, args+2, &title); if(SUCCEEDED(hres) && args_cnt > 3) { FIXME("unsupported arg_cnt %d\n", args_cnt); @@ -2493,7 +2493,7 @@ static HRESULT Global_DateAdd(BuiltinDisp *This, VARIANT *args, unsigned args_cn if (V_VT(args + 2) == VT_NULL) return return_null(res); - hres = to_string(args, &interval); + hres = to_string(This->ctx->lcid, args, &interval); if (SUCCEEDED(hres)) hres = to_int(args + 1, &count); if (SUCCEEDED(hres)) @@ -2690,7 +2690,7 @@ static HRESULT Global_Filter(BuiltinDisp *This, VARIANT *args, unsigned args_cnt if(V_VT(args+1) == VT_BSTR) { search = V_BSTR(args+1); }else { - hres = to_string(args+1, &conv_search); + hres = to_string(This->ctx->lcid, args+1, &conv_search); if(FAILED(hres)) return hres; search = conv_search; @@ -2745,7 +2745,7 @@ static HRESULT Global_Filter(BuiltinDisp *This, VARIANT *args, unsigned args_cnt if(V_VT(&data[i]) == VT_BSTR) { str = V_BSTR(&data[i]); }else { - hres = to_string(&data[i], &conv_str); + hres = to_string(This->ctx->lcid, &data[i], &conv_str); if(FAILED(hres)) { SafeArrayUnaccessData(sa); goto done; @@ -2854,7 +2854,7 @@ static HRESULT Global_Join(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if (V_VT(args + 1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); if (V_VT(args + 1) != VT_BSTR) { - hres = to_string(args + 1, &free_delimiter); + hres = to_string(This->ctx->lcid, args + 1, &free_delimiter); if (FAILED(hres)) return hres; delimiter = free_delimiter; @@ -2890,7 +2890,7 @@ static HRESULT Global_Join(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, } for (i = 0; i < count; i++) { if (V_VT(&data[i]) != VT_BSTR) { - hres = to_string(&data[i], &str); + hres = to_string(This->ctx->lcid, &data[i], &str); if (FAILED(hres)) goto cleanup_data; } else { @@ -2956,7 +2956,7 @@ static HRESULT Global_Split(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &string); + hres = to_string(This->ctx->lcid, args, &string); if(FAILED(hres)) return hres; }else { @@ -2965,7 +2965,7 @@ static HRESULT Global_Split(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(args_cnt > 1) { if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &delimiter); + hres = to_string(This->ctx->lcid, args+1, &delimiter); if(FAILED(hres)) goto done; }else { @@ -3106,7 +3106,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &string); + hres = to_string(This->ctx->lcid, args, &string); if(FAILED(hres)) return hres; }else { @@ -3114,7 +3114,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn } if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &find); + hres = to_string(This->ctx->lcid, args+1, &find); if(FAILED(hres)) goto error; }else { @@ -3122,7 +3122,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn } if(V_VT(args+2) != VT_BSTR) { - hres = to_string(args+2, &replace); + hres = to_string(This->ctx->lcid, args+2, &replace); if(FAILED(hres)) goto error; }else { @@ -3187,7 +3187,7 @@ static HRESULT Global_StrReverse(BuiltinDisp *This, VARIANT *arg, unsigned args_ TRACE("%s\n", debugstr_variant(arg)); - hres = to_string(arg, &ret); + hres = to_string(This->ctx->lcid, arg, &ret); if(FAILED(hres)) return hres; @@ -3234,7 +3234,7 @@ static HRESULT Global_InStrRev(BuiltinDisp *This, VARIANT *args, unsigned args_c } if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &str1); + hres = to_string(This->ctx->lcid, args, &str1); if(FAILED(hres)) return hres; } @@ -3242,7 +3242,7 @@ static HRESULT Global_InStrRev(BuiltinDisp *This, VARIANT *args, unsigned args_c str1 = V_BSTR(args); if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &str2); + hres = to_string(This->ctx->lcid, args+1, &str2); if(FAILED(hres)) { if(V_VT(args) != VT_BSTR) SysFreeString(str1); @@ -3336,15 +3336,13 @@ static HRESULT format_number_lcid(LCID lcid, VARIANT *var, int ndigits, int nlea { WCHAR buff[256], decimal[8], thousands[8]; NUMBERFMTW fmt; - VARIANT v; + BSTR str; HRESULT hres; *out = NULL; - V_VT(&v) = VT_EMPTY; - hres = VariantCopyInd(&v, var); - if(FAILED(hres)) return hres; - hres = VariantChangeTypeEx(&v, &v, LCID_EN_US, 0, VT_BSTR); - if(FAILED(hres)) return hres; + hres = to_string(LCID_EN_US, var, &str); + if(FAILED(hres)) + return hres; if(ndigits < 0) LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); @@ -3374,11 +3372,11 @@ static HRESULT format_number_lcid(LCID lcid, VARIANT *var, int ndigits, int nlea fmt.lpThousandSep = thousands; GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); - if(!GetNumberFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { - SysFreeString(V_BSTR(&v)); + if(!GetNumberFormatW(lcid, 0, str, &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(str); return DISP_E_TYPEMISMATCH; } - SysFreeString(V_BSTR(&v)); + SysFreeString(str); *out = SysAllocString(buff); return *out ? S_OK : E_OUTOFMEMORY; @@ -3389,23 +3387,23 @@ static HRESULT format_currency_lcid(LCID lcid, VARIANT *var, int ndigits, int nl { WCHAR buff[256], decimal[8], thousands[4], currency[13]; CURRENCYFMTW fmt; - VARIANT v; + BSTR str; HRESULT hres; - CY cy; *out = NULL; if(V_VT(var) == VT_BSTR || V_VT(var) == (VT_BSTR|VT_BYREF)) { + VARIANT v; + CY cy; hres = VarCyFromStr(V_ISBYREF(var) ? *V_BSTRREF(var) : V_BSTR(var), lcid, 0, &cy); if(FAILED(hres)) return hres; V_VT(&v) = VT_CY; V_CY(&v) = cy; + hres = to_string(lcid, &v, &str); }else { - V_VT(&v) = VT_EMPTY; - hres = VariantCopyInd(&v, var); - if(FAILED(hres)) return hres; + hres = to_string(lcid, var, &str); } - hres = VariantChangeTypeEx(&v, &v, lcid, 0, VT_BSTR); - if(FAILED(hres)) return hres; + if(FAILED(hres)) + return hres; if(ndigits < 0) LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); @@ -3438,11 +3436,11 @@ static HRESULT format_currency_lcid(LCID lcid, VARIANT *var, int ndigits, int nl fmt.lpCurrencySymbol = currency; GetLocaleInfoW(lcid, LOCALE_SCURRENCY, currency, ARRAY_SIZE(currency)); - if(!GetCurrencyFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { - SysFreeString(V_BSTR(&v)); + if(!GetCurrencyFormatW(lcid, 0, str, &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(str); return DISP_E_TYPEMISMATCH; } - SysFreeString(V_BSTR(&v)); + SysFreeString(str); *out = SysAllocString(buff); return *out ? S_OK : E_OUTOFMEMORY; @@ -3745,7 +3743,7 @@ static HRESULT Global_Escape(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -3810,7 +3808,7 @@ static HRESULT Global_Unescape(BuiltinDisp *This, VARIANT *arg, unsigned args_cn if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -3881,7 +3879,7 @@ static HRESULT dispatch_to_string(script_ctx_t *ctx, IDispatch *disp, BSTR *ret) *ret = V_BSTR(&v); return S_OK; } - hres = to_string(&v, ret); + hres = to_string(ctx->lcid, &v, ret); VariantClear(&v); return hres; } @@ -4274,7 +4272,7 @@ static const builtin_prop_t global_props[] = { {L"Year", Global_Year, 0, 1} }; -static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VARIANT *res) +static HRESULT err_string_prop(BuiltinDisp *This, BSTR *prop, VARIANT *args, unsigned args_cnt, VARIANT *res) { BSTR str; HRESULT hres; @@ -4282,7 +4280,7 @@ static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VAR if(!args_cnt) return return_string(res, *prop ? *prop : L""); - hres = to_string(args, &str); + hres = to_string(This->ctx->lcid, args, &str); if(FAILED(hres)) return hres; @@ -4294,7 +4292,7 @@ static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VAR static HRESULT Err_Description(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrDescription, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrDescription, args, args_cnt, res); } static HRESULT Err_HelpContext(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4312,7 +4310,7 @@ static HRESULT Err_HelpContext(BuiltinDisp *This, VARIANT *args, unsigned args_c static HRESULT Err_HelpFile(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrHelpFile, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrHelpFile, args, args_cnt, res); } static HRESULT Err_Number(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4333,7 +4331,7 @@ static HRESULT Err_Number(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, V static HRESULT Err_Source(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrSource, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrSource, args, args_cnt, res); } static HRESULT Err_Clear(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4359,11 +4357,11 @@ static HRESULT Err_Raise(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VA return E_INVALIDARG; if(args_cnt >= 2) - hres = to_string(args + 1, &source); + hres = to_string(This->ctx->lcid, args + 1, &source); if(args_cnt >= 3 && SUCCEEDED(hres)) - hres = to_string(args + 2, &description); + hres = to_string(This->ctx->lcid, args + 2, &description); if(args_cnt >= 4 && SUCCEEDED(hres)) - hres = to_string(args + 3, &helpfile); + hres = to_string(This->ctx->lcid, args + 3, &helpfile); if(args_cnt >= 5 && SUCCEEDED(hres)) hres = to_int(args + 4, &helpcontext); diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 3b290a5a014..618d47930a6 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -3612,10 +3612,11 @@ Call ok(GetLocale() = 1041, "GetLocale after SetLocale(1041)") Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber ja-JP: " & FormatNumber(1234567.89)) Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent ja-JP: " & FormatPercent(0.1234)) -' CStr goes through VariantChangeType with LCID=0 (LOCALE_USER_DEFAULT). -' Plumbing ctx->lcid into that path is follow-up work. +' Variant coercion to string (CStr etc.) uses the script LCID. Call SetLocale(1031) -Call todo_wine_ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) +Call ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) +Call SetLocale(1033) +Call ok(CStr(1.5) = "1.5", "CStr(1.5) en-US: " & CStr(1.5)) ' Restore original locale. Call SetLocale(origLcid) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)