// Copyright 2010 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "i18n-extension.h" #include #include #include "break-iterator.h" #include "unicode/locid.h" #include "unicode/uloc.h" namespace v8 { namespace internal { I18NExtension* I18NExtension::extension_ = NULL; // TODO(cira): maybe move JS code to a .js file and generata cc files from it? // TODO(cira): Remove v8 prefix from v8Locale once we have stable API. const char* const I18NExtension::kSource = "v8Locale = function(optLocale) {" " native function NativeJSLocale();" " var properties = NativeJSLocale(optLocale);" " this.locale = properties.locale;" " this.language = properties.language;" " this.script = properties.script;" " this.region = properties.region;" "};" "v8Locale.availableLocales = function() {" " native function NativeJSAvailableLocales();" " return NativeJSAvailableLocales();" "};" "v8Locale.prototype.maximizedLocale = function() {" " native function NativeJSMaximizedLocale();" " return new v8Locale(NativeJSMaximizedLocale(this.locale));" "};" "v8Locale.prototype.minimizedLocale = function() {" " native function NativeJSMinimizedLocale();" " return new v8Locale(NativeJSMinimizedLocale(this.locale));" "};" "v8Locale.prototype.displayLocale_ = function(displayLocale) {" " var result = this.locale;" " if (displayLocale !== undefined) {" " result = displayLocale.locale;" " }" " return result;" "};" "v8Locale.prototype.displayLanguage = function(optDisplayLocale) {" " var displayLocale = this.displayLocale_(optDisplayLocale);" " native function NativeJSDisplayLanguage();" " return NativeJSDisplayLanguage(this.locale, displayLocale);" "};" "v8Locale.prototype.displayScript = function(optDisplayLocale) {" " var displayLocale = this.displayLocale_(optDisplayLocale);" " native function NativeJSDisplayScript();" " return NativeJSDisplayScript(this.locale, displayLocale);" "};" "v8Locale.prototype.displayRegion = function(optDisplayLocale) {" " var displayLocale = this.displayLocale_(optDisplayLocale);" " native function NativeJSDisplayRegion();" " return NativeJSDisplayRegion(this.locale, displayLocale);" "};" "v8Locale.prototype.displayName = function(optDisplayLocale) {" " var displayLocale = this.displayLocale_(optDisplayLocale);" " native function NativeJSDisplayName();" " return NativeJSDisplayName(this.locale, displayLocale);" "};" "v8Locale.v8BreakIterator = function(locale, type) {" " native function NativeJSBreakIterator();" " var iterator = NativeJSBreakIterator(locale, type);" " iterator.type = type;" " return iterator;" "};" "v8Locale.v8BreakIterator.BreakType = {" " 'unknown': -1," " 'none': 0," " 'number': 100," " 'word': 200," " 'kana': 300," " 'ideo': 400" "};" "v8Locale.prototype.v8CreateBreakIterator = function(type) {" " return new v8Locale.v8BreakIterator(this.locale, type);" "};"; v8::Handle I18NExtension::GetNativeFunction( v8::Handle name) { if (name->Equals(v8::String::New("NativeJSLocale"))) { return v8::FunctionTemplate::New(JSLocale); } else if (name->Equals(v8::String::New("NativeJSAvailableLocales"))) { return v8::FunctionTemplate::New(JSAvailableLocales); } else if (name->Equals(v8::String::New("NativeJSMaximizedLocale"))) { return v8::FunctionTemplate::New(JSMaximizedLocale); } else if (name->Equals(v8::String::New("NativeJSMinimizedLocale"))) { return v8::FunctionTemplate::New(JSMinimizedLocale); } else if (name->Equals(v8::String::New("NativeJSDisplayLanguage"))) { return v8::FunctionTemplate::New(JSDisplayLanguage); } else if (name->Equals(v8::String::New("NativeJSDisplayScript"))) { return v8::FunctionTemplate::New(JSDisplayScript); } else if (name->Equals(v8::String::New("NativeJSDisplayRegion"))) { return v8::FunctionTemplate::New(JSDisplayRegion); } else if (name->Equals(v8::String::New("NativeJSDisplayName"))) { return v8::FunctionTemplate::New(JSDisplayName); } else if (name->Equals(v8::String::New("NativeJSBreakIterator"))) { return v8::FunctionTemplate::New(BreakIterator::JSBreakIterator); } return v8::Handle(); } v8::Handle I18NExtension::JSLocale(const v8::Arguments& args) { // TODO(cira): Fetch browser locale. Accept en-US as good default for now. // We could possibly pass browser locale as a parameter in the constructor. std::string locale_name("en-US"); if (args.Length() == 1 && args[0]->IsString()) { locale_name = *v8::String::Utf8Value(args[0]->ToString()); } v8::Local locale = v8::Object::New(); locale->Set(v8::String::New("locale"), v8::String::New(locale_name.c_str())); icu::Locale icu_locale(locale_name.c_str()); const char* language = icu_locale.getLanguage(); locale->Set(v8::String::New("language"), v8::String::New(language)); const char* script = icu_locale.getScript(); if (strlen(script)) { locale->Set(v8::String::New("script"), v8::String::New(script)); } const char* region = icu_locale.getCountry(); if (strlen(region)) { locale->Set(v8::String::New("region"), v8::String::New(region)); } return locale; } // TODO(cira): Filter out locales that Chrome doesn't support. v8::Handle I18NExtension::JSAvailableLocales( const v8::Arguments& args) { v8::Local all_locales = v8::Array::New(); int count = 0; const icu::Locale* icu_locales = icu::Locale::getAvailableLocales(count); for (int i = 0; i < count; ++i) { all_locales->Set(i, v8::String::New(icu_locales[i].getName())); } return all_locales; } // Use - as tag separator, not _ that ICU uses. static std::string NormalizeLocale(const std::string& locale) { std::string result(locale); // TODO(cira): remove STL dependency. std::replace(result.begin(), result.end(), '_', '-'); return result; } v8::Handle I18NExtension::JSMaximizedLocale( const v8::Arguments& args) { if (!args.Length() || !args[0]->IsString()) { return v8::Undefined(); } UErrorCode status = U_ZERO_ERROR; std::string locale_name = *v8::String::Utf8Value(args[0]->ToString()); char max_locale[ULOC_FULLNAME_CAPACITY]; uloc_addLikelySubtags(locale_name.c_str(), max_locale, sizeof(max_locale), &status); if (U_FAILURE(status)) { return v8::Undefined(); } return v8::String::New(NormalizeLocale(max_locale).c_str()); } v8::Handle I18NExtension::JSMinimizedLocale( const v8::Arguments& args) { if (!args.Length() || !args[0]->IsString()) { return v8::Undefined(); } UErrorCode status = U_ZERO_ERROR; std::string locale_name = *v8::String::Utf8Value(args[0]->ToString()); char min_locale[ULOC_FULLNAME_CAPACITY]; uloc_minimizeSubtags(locale_name.c_str(), min_locale, sizeof(min_locale), &status); if (U_FAILURE(status)) { return v8::Undefined(); } return v8::String::New(NormalizeLocale(min_locale).c_str()); } // Common code for JSDisplayXXX methods. static v8::Handle GetDisplayItem(const v8::Arguments& args, const std::string& item) { if (args.Length() != 2 || !args[0]->IsString() || !args[1]->IsString()) { return v8::Undefined(); } std::string base_locale = *v8::String::Utf8Value(args[0]->ToString()); icu::Locale icu_locale(base_locale.c_str()); icu::Locale display_locale = icu::Locale(*v8::String::Utf8Value(args[1]->ToString())); icu::UnicodeString result; if (item == "language") { icu_locale.getDisplayLanguage(display_locale, result); } else if (item == "script") { icu_locale.getDisplayScript(display_locale, result); } else if (item == "region") { icu_locale.getDisplayCountry(display_locale, result); } else if (item == "name") { icu_locale.getDisplayName(display_locale, result); } else { return v8::Undefined(); } if (result.length()) { return v8::String::New( reinterpret_cast(result.getBuffer()), result.length()); } return v8::Undefined(); } v8::Handle I18NExtension::JSDisplayLanguage( const v8::Arguments& args) { return GetDisplayItem(args, "language"); } v8::Handle I18NExtension::JSDisplayScript( const v8::Arguments& args) { return GetDisplayItem(args, "script"); } v8::Handle I18NExtension::JSDisplayRegion( const v8::Arguments& args) { return GetDisplayItem(args, "region"); } v8::Handle I18NExtension::JSDisplayName(const v8::Arguments& args) { return GetDisplayItem(args, "name"); } I18NExtension* I18NExtension::get() { if (!extension_) { extension_ = new I18NExtension(); } return extension_; } void I18NExtension::Register() { static v8::DeclareExtension i18n_extension_declaration(I18NExtension::get()); } } } // namespace v8::internal