зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1339892 - Refactor mozIntl to have a nicer API and thin logic. r=smaug
MozReview-Commit-ID: J4QXXBy7JII --HG-- rename : toolkit/components/mozintl/MozIntl.cpp => toolkit/components/mozintl/MozIntlHelper.cpp rename : toolkit/components/mozintl/MozIntl.h => toolkit/components/mozintl/MozIntlHelper.h rename : toolkit/components/mozintl/mozIMozIntl.idl => toolkit/components/mozintl/mozIMozIntlHelper.idl extra : rebase_source : 9753dbeea2e11c7fde550df1a20b1c3d1b2063fe
This commit is contained in:
Родитель
28079200f3
Коммит
3613954ec3
|
@ -550,6 +550,11 @@
|
|||
@RESPATH@/components/PresentationDataChannelSessionTransport.js
|
||||
@RESPATH@/components/PresentationDataChannelSessionTransport.manifest
|
||||
|
||||
#ifdef ENABLE_INTL_API
|
||||
@RESPATH@/components/mozIntl.manifest
|
||||
@RESPATH@/components/mozIntl.js
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_TESTS) && defined(MOZ_DEBUG)
|
||||
@RESPATH@/components/TestInterfaceJS.js
|
||||
@RESPATH@/components/TestInterfaceJS.manifest
|
||||
|
|
|
@ -398,6 +398,11 @@
|
|||
@BINPATH@/components/TVSimulatorService.js
|
||||
@BINPATH@/components/TVSimulatorService.manifest
|
||||
|
||||
#ifdef ENABLE_INTL_API
|
||||
@BINPATH@/components/mozIntl.manifest
|
||||
@BINPATH@/components/mozIntl.js
|
||||
#endif
|
||||
|
||||
; Modules
|
||||
@BINPATH@/modules/*
|
||||
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MozIntl.h"
|
||||
#include "MozIntlHelper.h"
|
||||
#include "jswrapper.h"
|
||||
#include "mozilla/ModuleUtils.h"
|
||||
|
||||
#define MOZ_MOZINTL_CID \
|
||||
{ 0x83f8f991, 0x6b81, 0x4dd8, { 0xa0, 0x93, 0x72, 0x0b, 0xfb, 0x67, 0x4d, 0x38 } }
|
||||
#define MOZ_MOZINTLHELPER_CID \
|
||||
{ 0xb43c96be, 0x2b3a, 0x4dc4, { 0x90, 0xe9, 0xb0, 0x6d, 0x34, 0x21, 0x9b, 0x68 } }
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
NS_IMPL_ISUPPORTS(MozIntl, mozIMozIntl)
|
||||
NS_IMPL_ISUPPORTS(MozIntlHelper, mozIMozIntlHelper)
|
||||
|
||||
MozIntl::MozIntl() = default;
|
||||
MozIntlHelper::MozIntlHelper() = default;
|
||||
|
||||
MozIntl::~MozIntl() = default;
|
||||
MozIntlHelper::~MozIntlHelper() = default;
|
||||
|
||||
static nsresult
|
||||
AddFunctions(JSContext* cx, JS::Handle<JS::Value> val, const JSFunctionSpec* funcs)
|
||||
|
@ -40,7 +40,7 @@ AddFunctions(JSContext* cx, JS::Handle<JS::Value> val, const JSFunctionSpec* fun
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
MozIntlHelper::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
{
|
||||
static const JSFunctionSpec funcs[] = {
|
||||
JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
|
||||
|
@ -51,7 +51,7 @@ MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
MozIntlHelper::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
{
|
||||
static const JSFunctionSpec funcs[] = {
|
||||
JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
|
||||
|
@ -62,7 +62,7 @@ MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MozIntl::AddPluralRulesConstructor(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
MozIntlHelper::AddPluralRulesConstructor(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
{
|
||||
if (!val.isObject()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
@ -83,7 +83,7 @@ MozIntl::AddPluralRulesConstructor(JS::Handle<JS::Value> val, JSContext* cx)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MozIntl::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
MozIntlHelper::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
{
|
||||
static const JSFunctionSpec funcs[] = {
|
||||
JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
|
||||
|
@ -93,27 +93,27 @@ MozIntl::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
|||
return AddFunctions(cx, val, funcs);
|
||||
}
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
|
||||
NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntlHelper)
|
||||
NS_DEFINE_NAMED_CID(MOZ_MOZINTLHELPER_CID);
|
||||
|
||||
static const Module::CIDEntry kMozIntlCIDs[] = {
|
||||
{ &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
|
||||
static const Module::CIDEntry kMozIntlHelperCIDs[] = {
|
||||
{ &kMOZ_MOZINTLHELPER_CID, false, nullptr, MozIntlHelperConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kMozIntlContracts[] = {
|
||||
{ "@mozilla.org/mozintl;1", &kMOZ_MOZINTL_CID },
|
||||
static const mozilla::Module::ContractIDEntry kMozIntlHelperContracts[] = {
|
||||
{ "@mozilla.org/mozintlhelper;1", &kMOZ_MOZINTLHELPER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kMozIntlModule = {
|
||||
static const mozilla::Module kMozIntlHelperModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kMozIntlCIDs,
|
||||
kMozIntlContracts,
|
||||
kMozIntlHelperCIDs,
|
||||
kMozIntlHelperContracts,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(mozMozIntlModule) = &kMozIntlModule;
|
||||
NSMODULE_DEFN(mozMozIntlHelperModule) = &kMozIntlHelperModule;
|
|
@ -3,20 +3,20 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozIMozIntl.h"
|
||||
#include "mozIMozIntlHelper.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MozIntl final : public mozIMozIntl
|
||||
class MozIntlHelper final : public mozIMozIntlHelper
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MOZIMOZINTL
|
||||
NS_DECL_MOZIMOZINTLHELPER
|
||||
|
||||
MozIntl();
|
||||
MozIntlHelper();
|
||||
|
||||
private:
|
||||
~MozIntl();
|
||||
~MozIntlHelper();
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
|
@ -8,12 +8,18 @@ XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
|
|||
|
||||
XPIDL_SOURCES += [
|
||||
'mozIMozIntl.idl',
|
||||
'mozIMozIntlHelper.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'mozintl'
|
||||
|
||||
SOURCES += [
|
||||
'MozIntl.cpp',
|
||||
'MozIntlHelper.cpp',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'mozIntl.js',
|
||||
'mozIntl.manifest',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
|
@ -5,17 +5,41 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
|
||||
/**
|
||||
* This is a set of APIs that are of general usefulness for user interface
|
||||
* internationalization.
|
||||
*
|
||||
* They're all in various stages of the standardization process through
|
||||
* ECMA402, so they are exposed to privileged content only but are written
|
||||
* in the way to allow for easy migration to standard Intl object once
|
||||
* the appropriate stage of the ECMA402 is achieved.
|
||||
*
|
||||
* The exact structure of the code is a little bit complex because of that:
|
||||
*
|
||||
* 1) The core is in SpiderMonkey together with other Intl APIs
|
||||
*
|
||||
* This allows us to write the code once, stick to the spec language
|
||||
* of the proposal, reuse our ICU bindings in Spidermonkey and use
|
||||
* the code to inform us on refining the spec proposal for the given API itself.
|
||||
*
|
||||
* 2) The MozIntlHelper API exposes the SpiderMonkey APIs
|
||||
*
|
||||
* This helper API allows attaching the new APIs on any regular object.
|
||||
*
|
||||
* 3) The MozIntl API provides the access to those APIs
|
||||
*
|
||||
* This API exposes the actual functionality and wraps around the MozIntlHelper
|
||||
* lazily retrieving and setting the accessors.
|
||||
* On top of that, the API also binds additional functionality like using
|
||||
* current application locale by default, and fetching OS regional preferences
|
||||
* for date time format.
|
||||
*/
|
||||
[scriptable, uuid(7f63279a-1a29-4ae6-9e7a-dc9684a23530)]
|
||||
interface mozIMozIntl : nsISupports
|
||||
{
|
||||
[implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
|
||||
[implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
|
||||
[implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
|
||||
jsval getCalendarInfo([optional] in jsval locales);
|
||||
jsval getDisplayNames([optional] in jsval locales, [optional] in jsval options);
|
||||
jsval getLocaleInfo([optional] in jsval locales);
|
||||
|
||||
/**
|
||||
* Adds a PluralRules constructor to the given object. This function may only
|
||||
* be called once within a realm/global object: calling it multiple times will
|
||||
* throw.
|
||||
*/
|
||||
[implicit_jscontext] void addPluralRulesConstructor(in jsval intlObject);
|
||||
jsval createPluralRules([optional] in jsval locales, [optional] in jsval options);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* This is an internal helper for mozIMozIntl API. There should be virtually
|
||||
* no reason for you to call this API except from mozIMozIntl implementation.
|
||||
*
|
||||
* This API helps accessing the SpiderMonkey Intl APIs, but it is mozIMozIntl
|
||||
* that exposes the thin wrapper around them that binds the functionality
|
||||
* to Gecko.
|
||||
*/
|
||||
[scriptable, uuid(189eaa7d-b29a-43a9-b1fb-7658990df940)]
|
||||
interface mozIMozIntlHelper : nsISupports
|
||||
{
|
||||
[implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
|
||||
[implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
|
||||
[implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
|
||||
|
||||
/**
|
||||
* Adds a PluralRules constructor to the given object. This function may only
|
||||
* be called once within a realm/global object: calling it multiple times will
|
||||
* throw.
|
||||
*/
|
||||
[implicit_jscontext] void addPluralRulesConstructor(in jsval intlObject);
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
const mozIntlHelper =
|
||||
Cc["@mozilla.org/mozintlhelper;1"].getService(Ci.mozIMozIntlHelper);
|
||||
const localeSvc =
|
||||
Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
|
||||
|
||||
/**
|
||||
* This helper function retrives currently used app locales, allowing
|
||||
* all mozIntl APIs to use the current app locales unless called with
|
||||
* explicitly listed locales.
|
||||
*/
|
||||
function getLocales(locales) {
|
||||
if (!locales) {
|
||||
return localeSvc.getAppLocales();
|
||||
}
|
||||
return locales;
|
||||
}
|
||||
|
||||
class MozIntl {
|
||||
constructor() {
|
||||
this._cache = {};
|
||||
}
|
||||
|
||||
getCalendarInfo(locales, ...args) {
|
||||
if (!this._cache.hasOwnProperty("getCalendarInfo")) {
|
||||
mozIntlHelper.addGetCalendarInfo(this._cache);
|
||||
}
|
||||
|
||||
return this._cache.getCalendarInfo(getLocales(locales), ...args);
|
||||
}
|
||||
|
||||
getDisplayNames(locales, ...args) {
|
||||
if (!this._cache.hasOwnProperty("getDisplayNames")) {
|
||||
mozIntlHelper.addGetDisplayNames(this._cache);
|
||||
}
|
||||
|
||||
return this._cache.getDisplayNames(getLocales(locales), ...args);
|
||||
}
|
||||
|
||||
getLocaleInfo(locales, ...args) {
|
||||
if (!this._cache.hasOwnProperty("getLocaleInfo")) {
|
||||
mozIntlHelper.addGetLocaleInfo(this._cache);
|
||||
}
|
||||
|
||||
return this._cache.getLocaleInfo(getLocales(locales), ...args);
|
||||
}
|
||||
|
||||
createPluralRules(locales, ...args) {
|
||||
if (!this._cache.hasOwnProperty("PluralRules")) {
|
||||
mozIntlHelper.addPluralRulesConstructor(this._cache);
|
||||
}
|
||||
|
||||
return new this._cache.PluralRules(getLocales(locales), ...args);
|
||||
}
|
||||
}
|
||||
|
||||
MozIntl.prototype.classID = Components.ID("{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}");
|
||||
MozIntl.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.mozIMozIntl, Ci.nsISupports]);
|
||||
|
||||
var components = [MozIntl];
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
|
@ -0,0 +1,2 @@
|
|||
component {35ec195a-e8d0-4300-83af-c8a2cc84b4a3} mozIntl.js
|
||||
contract @mozilla.org/mozintl;1 {35ec195a-e8d0-4300-83af-c8a2cc84b4a3}
|
|
@ -5,46 +5,23 @@ function run_test() {
|
|||
const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
|
||||
.getService(Components.interfaces.mozIMozIntl);
|
||||
|
||||
test_this_global(mozIntl);
|
||||
test_cross_global(mozIntl);
|
||||
test_methods_presence(mozIntl);
|
||||
test_methods_calling(mozIntl);
|
||||
|
||||
ok(true);
|
||||
}
|
||||
|
||||
function test_this_global(mozIntl) {
|
||||
let x = {};
|
||||
|
||||
mozIntl.addGetCalendarInfo(x);
|
||||
equal(x.getCalendarInfo instanceof Function, true);
|
||||
equal(x.getCalendarInfo() instanceof Object, true);
|
||||
}
|
||||
|
||||
function test_cross_global(mozIntl) {
|
||||
var global = new Components.utils.Sandbox("https://example.com/");
|
||||
var x = global.Object();
|
||||
|
||||
mozIntl.addGetCalendarInfo(x);
|
||||
var waivedX = Components.utils.waiveXrays(x);
|
||||
equal(waivedX.getCalendarInfo instanceof Function, false);
|
||||
equal(waivedX.getCalendarInfo instanceof global.Function, true);
|
||||
equal(waivedX.getCalendarInfo() instanceof Object, false);
|
||||
equal(waivedX.getCalendarInfo() instanceof global.Object, true);
|
||||
}
|
||||
|
||||
function test_methods_presence(mozIntl) {
|
||||
equal(mozIntl.addGetCalendarInfo instanceof Function, true);
|
||||
equal(mozIntl.addGetDisplayNames instanceof Function, true);
|
||||
equal(mozIntl.addGetLocaleInfo instanceof Function, true);
|
||||
|
||||
let x = {};
|
||||
|
||||
mozIntl.addGetCalendarInfo(x);
|
||||
equal(x.getCalendarInfo instanceof Function, true);
|
||||
|
||||
mozIntl.addGetDisplayNames(x);
|
||||
equal(x.getDisplayNames instanceof Function, true);
|
||||
|
||||
mozIntl.addGetLocaleInfo(x);
|
||||
equal(x.getLocaleInfo instanceof Function, true);
|
||||
equal(mozIntl.getCalendarInfo instanceof Function, true);
|
||||
equal(mozIntl.getDisplayNames instanceof Function, true);
|
||||
equal(mozIntl.getLocaleInfo instanceof Function, true);
|
||||
equal(mozIntl.createPluralRules instanceof Function, true);
|
||||
}
|
||||
|
||||
function test_methods_calling(mozIntl) {
|
||||
let ci = mozIntl.getCalendarInfo('pl');
|
||||
let dn = mozIntl.getDisplayNames('ar');
|
||||
let li = mozIntl.getLocaleInfo('de');
|
||||
let pr = mozIntl.createPluralRules('fr');
|
||||
ok(true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
const miHelper = Components.classes["@mozilla.org/mozintlhelper;1"]
|
||||
.getService(Components.interfaces.mozIMozIntlHelper);
|
||||
|
||||
test_this_global(miHelper);
|
||||
test_cross_global(miHelper);
|
||||
test_methods_presence(miHelper);
|
||||
|
||||
ok(true);
|
||||
}
|
||||
|
||||
function test_this_global(miHelper) {
|
||||
let x = {};
|
||||
|
||||
miHelper.addGetCalendarInfo(x);
|
||||
equal(x.getCalendarInfo instanceof Function, true);
|
||||
equal(x.getCalendarInfo() instanceof Object, true);
|
||||
}
|
||||
|
||||
function test_cross_global(miHelper) {
|
||||
var global = new Components.utils.Sandbox("https://example.com/");
|
||||
var x = global.Object();
|
||||
|
||||
miHelper.addGetCalendarInfo(x);
|
||||
var waivedX = Components.utils.waiveXrays(x);
|
||||
equal(waivedX.getCalendarInfo instanceof Function, false);
|
||||
equal(waivedX.getCalendarInfo instanceof global.Function, true);
|
||||
equal(waivedX.getCalendarInfo() instanceof Object, false);
|
||||
equal(waivedX.getCalendarInfo() instanceof global.Object, true);
|
||||
}
|
||||
|
||||
function test_methods_presence(miHelper) {
|
||||
equal(miHelper.addGetCalendarInfo instanceof Function, true);
|
||||
equal(miHelper.addGetDisplayNames instanceof Function, true);
|
||||
equal(miHelper.addGetLocaleInfo instanceof Function, true);
|
||||
|
||||
let x = {};
|
||||
|
||||
miHelper.addGetCalendarInfo(x);
|
||||
equal(x.getCalendarInfo instanceof Function, true);
|
||||
|
||||
miHelper.addGetDisplayNames(x);
|
||||
equal(x.getDisplayNames instanceof Function, true);
|
||||
|
||||
miHelper.addGetLocaleInfo(x);
|
||||
equal(x.getLocaleInfo instanceof Function, true);
|
||||
}
|
|
@ -2,3 +2,4 @@
|
|||
head =
|
||||
|
||||
[test_mozintl.js]
|
||||
[test_mozintlhelper.js]
|
||||
|
|
|
@ -23,11 +23,8 @@
|
|||
<field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
|
||||
<field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
|
||||
<constructor><![CDATA[
|
||||
this.l10n = {};
|
||||
const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
|
||||
this.mozIntl = Components.classes["@mozilla.org/mozintl;1"]
|
||||
.getService(Components.interfaces.mozIMozIntl);
|
||||
mozIntl.addGetCalendarInfo(this.l10n);
|
||||
mozIntl.addGetDisplayNames(this.l10n);
|
||||
]]></constructor>
|
||||
<method name="loadPicker">
|
||||
<parameter name="type"/>
|
||||
|
@ -226,7 +223,7 @@
|
|||
<method name="getCalendarInfo">
|
||||
<parameter name="locale"/>
|
||||
<body><![CDATA[
|
||||
const calendarInfo = this.l10n.getCalendarInfo(locale);
|
||||
const calendarInfo = this.mozIntl.getCalendarInfo(locale);
|
||||
|
||||
// Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
|
||||
// so they need to be mapped to JavaScript convention with 0 as Sunday
|
||||
|
@ -259,7 +256,7 @@
|
|||
<parameter name="keys"/>
|
||||
<parameter name="style"/>
|
||||
<body><![CDATA[
|
||||
const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
|
||||
const displayNames = this.mozIntl.getDisplayNames(locale, {keys, style});
|
||||
return keys.map(key => displayNames.values[key]);
|
||||
]]></body>
|
||||
</method>
|
||||
|
|
Загрузка…
Ссылка в новой задаче