chromium/ui/base/l10n/l10n_util_ios.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ui/base/l10n/l10n_util_ios.h"

#import <Foundation/Foundation.h>

#include "base/check.h"
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_local.h"

namespace l10n_util {
namespace {

// Class used to implement a per thread cache of the NSLocale.
// TODO(http://www.openradar.me/39659413): It would be nice to simplify most of
// this class to a simple `thread_local NSLocale*`, but that causes Clang to
// emit "error: thread-local variable has non-trivial ownership: type is
// 'NSLocale *__strong'".
class LocaleCache {
 public:
  static NSLocale* LocaleForIdentifier(NSString* identifier) {
    using TLSLocaleCache = base::ThreadLocalOwnedPointer<LocaleCache>;
    static base::NoDestructor<TLSLocaleCache> gInstance;

    TLSLocaleCache& tls_cache = *gInstance;
    LocaleCache* cache = tls_cache.Get();
    if (!cache) {
      tls_cache.Set(std::make_unique<LocaleCache>());
      cache = tls_cache.Get();
    }

    DCHECK(cache);
    return cache->LocaleForIdentifierInternal(identifier);
  }

 private:
  NSLocale* LocaleForIdentifierInternal(NSString* identifier) {
    if (!locale_ || ![locale_.localeIdentifier isEqualToString:identifier]) {
      locale_ = [[NSLocale alloc] initWithLocaleIdentifier:identifier];
    }

    DCHECK(locale_);
    return locale_;
  }

  __strong NSLocale* locale_;
};

// Helper version of GetDisplayNameForLocale() operating on NSString*.
// Note: this function may be called from any thread and it *must* be
// thread-safe. Thus attention must be paid if any caching is introduced.
NSString* GetDisplayNameForLocale(NSString* language,
                                  NSString* display_locale) {
  NSLocale* ns_locale = LocaleCache::LocaleForIdentifier(display_locale);
  NSString* localized_language_name =
      [ns_locale displayNameForKey:NSLocaleIdentifier value:language];

  // Return localized language if system API provided it. Do not attempt to
  // manually parse into error format if no |locale| was provided.
  if (localized_language_name.length || !language.length) {
    return localized_language_name;
  }

  NSRange script_seperator = [language rangeOfString:@"-"
                                             options:NSBackwardsSearch];
  if (!script_seperator.length) {
    return [language lowercaseString];
  }

  NSString* language_component =
      [language substringToIndex:script_seperator.location];
  NSString* script_component = [language
      substringFromIndex:script_seperator.location + script_seperator.length];
  if (!script_component.length) {
    return [language_component lowercaseString];
  }

  return [NSString stringWithFormat:@"%@ (%@)",
                                    [language_component lowercaseString],
                                    [script_component uppercaseString]];
}

}  // namespace

std::u16string GetDisplayNameForLocale(const std::string& locale,
                                       const std::string& display_locale) {
  return base::SysNSStringToUTF16(
      GetDisplayNameForLocale(base::SysUTF8ToNSString(locale),
                              base::SysUTF8ToNSString(display_locale)));
}

}  // namespace l10n_util