mirror of
https://github.com/google/pebble.git
synced 2025-03-15 08:41:21 +00:00
596 lines
18 KiB
C
596 lines
18 KiB
C
|
/*-
|
||
|
* Copyright (c) 2000, 2001 Citrus Project,
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. 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.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "i18n.h"
|
||
|
#include "mo.h"
|
||
|
#include "kernel/event_loop.h"
|
||
|
#include "kernel/pbl_malloc.h"
|
||
|
#include "resource/resource.h"
|
||
|
#include "services/normal/filesystem/pfs.h"
|
||
|
#include "shell/normal/language_ui.h"
|
||
|
#include "shell/prefs.h"
|
||
|
#include "system/logging.h"
|
||
|
#include "system/passert.h"
|
||
|
#include "util/list.h"
|
||
|
|
||
|
//////////////////////////////////////////////////////
|
||
|
// See mo.h for a description of the MO file format //
|
||
|
//////////////////////////////////////////////////////
|
||
|
|
||
|
typedef struct {
|
||
|
uint32_t hash;
|
||
|
const char *string;
|
||
|
const void *owner;
|
||
|
} StringLookupInfo;
|
||
|
|
||
|
static struct DomainBinding {
|
||
|
uint32_t resource_id;
|
||
|
ResourceCallbackHandle watch_handle;
|
||
|
bool need_reload;
|
||
|
ResourceVersion version;
|
||
|
MoHandle mohandle;
|
||
|
I18nString *strings_list;
|
||
|
char iso_locale[ISO_LOCALE_LENGTH];
|
||
|
char lang_name[LOCALE_NAME_LENGTH];
|
||
|
uint16_t lang_version;
|
||
|
} s_system_domain;
|
||
|
|
||
|
static void prv_list_flush(void);
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
// MO File Hash Table
|
||
|
|
||
|
uint32_t prv_gettext_hash(const char *str) {
|
||
|
const uint8_t *p;
|
||
|
uint32_t hash = 0, tmp;
|
||
|
|
||
|
for (p = (const uint8_t *)str; *p; p++) {
|
||
|
hash <<= 4;
|
||
|
hash += *p;
|
||
|
tmp = hash & 0xF0000000;
|
||
|
if (tmp != 0) {
|
||
|
hash ^= tmp;
|
||
|
hash ^= tmp >> 24;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hash;
|
||
|
}
|
||
|
|
||
|
static uint32_t prv_collision_step(uint32_t hashval, uint32_t hashsize) {
|
||
|
return (hashval % (hashsize - 2)) + 1;
|
||
|
}
|
||
|
|
||
|
static uint32_t prv_next_index(uint32_t curidx, uint32_t hashsize, uint32_t step) {
|
||
|
return curidx + step - (curidx >= hashsize - step ? hashsize : 0);
|
||
|
}
|
||
|
|
||
|
//! Lookup a translated string.
|
||
|
//! @param rlen[out] Can be NULL. If non-null will be populated with the length of the translated
|
||
|
//! string.
|
||
|
//! @param rstring[out] Can be NULL. If non-null this buffer will be populated with the translated
|
||
|
//! string. This buffer will be null-terminated.
|
||
|
//! @param rstring_len The length of the rstring buffer.
|
||
|
static void prv_lookup(const char *msgid, struct DomainBinding *db,
|
||
|
size_t *rlen, char *rstring, size_t rstring_len) {
|
||
|
MoHandle *mohandle = &db->mohandle;
|
||
|
*rlen = 0;
|
||
|
|
||
|
if (mohandle->mo.hdr.mo_hsize <= 2 || mohandle->mo.mo_htable == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uint32_t hashval = prv_gettext_hash(msgid);
|
||
|
uint32_t step = prv_collision_step(hashval, mohandle->mo.hdr.mo_hsize);
|
||
|
uint32_t idx = hashval % mohandle->mo.hdr.mo_hsize;
|
||
|
size_t len = strlen(msgid);
|
||
|
while (1) {
|
||
|
uint32_t strno = mohandle->mo.mo_htable[idx];
|
||
|
if (strno-- == 0) {
|
||
|
/* unexpected miss */
|
||
|
return;
|
||
|
}
|
||
|
MoEntry oentry;
|
||
|
if (resource_load_byte_range_system(0, db->resource_id, mohandle->mo.hdr.mo_otable
|
||
|
+ sizeof(MoEntry) * strno, (uint8_t *)&oentry, sizeof(MoEntry)) != sizeof(MoEntry)) {
|
||
|
return;
|
||
|
}
|
||
|
if (len == oentry.len) {
|
||
|
// Length of original matches, compare the contents
|
||
|
char key[oentry.len + 1];
|
||
|
if (resource_load_byte_range_system(0, db->resource_id, oentry.off, (uint8_t *)key,
|
||
|
oentry.len) != oentry.len) {
|
||
|
return;
|
||
|
}
|
||
|
key[oentry.len] = '\0';
|
||
|
|
||
|
if (!strcmp(msgid, key)) {
|
||
|
// Contents of original string matches, get the translated string
|
||
|
MoEntry tentry;
|
||
|
if (resource_load_byte_range_system(0, db->resource_id, mohandle->mo.hdr.mo_ttable
|
||
|
+ sizeof(MoEntry) * strno, (uint8_t *)&tentry, sizeof(MoEntry)) != sizeof(MoEntry)) {
|
||
|
return;
|
||
|
}
|
||
|
if (rstring) { // If we want the translated string, copy it out.
|
||
|
// Make sure we don't read out more than the length of the buffer we're reading into.
|
||
|
// Leave space for the null-terminator as well.
|
||
|
const size_t read_length = MIN(tentry.len, rstring_len - 1);
|
||
|
|
||
|
if (resource_load_byte_range_system(0, db->resource_id, tentry.off,
|
||
|
(uint8_t *)rstring, read_length) != read_length) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
rstring[read_length] = '\0';
|
||
|
}
|
||
|
if (rlen) { // If we want the translated string length, copy it out.
|
||
|
*rlen = tentry.len;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
idx = prv_next_index(idx, mohandle->mo.hdr.mo_hsize, step);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
// MO File Mapping & Lookup
|
||
|
|
||
|
static bool prv_get_property(const char *header, const char *name, char *buffer, size_t size) {
|
||
|
// Isolate the language name
|
||
|
char *str = strstr(header, name);
|
||
|
if (str == NULL) { // strstr failed
|
||
|
return false;
|
||
|
}
|
||
|
str += strlen(name);
|
||
|
|
||
|
char *end = strchr(str, '\n');
|
||
|
unsigned int length = end - str;
|
||
|
if (end == NULL || length > size) { // strchr failed
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
memcpy(buffer, str, length);
|
||
|
buffer[length] = '\0';
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool prv_get_metadata(struct DomainBinding *db) {
|
||
|
const size_t HEADER_BUFFER_SIZE = 400;
|
||
|
// malloc a comfortable amount of RAM to save the header in
|
||
|
char *header = kernel_malloc_check(HEADER_BUFFER_SIZE);
|
||
|
size_t header_len = 0;
|
||
|
bool success = false;
|
||
|
|
||
|
// all metadata is in the "" header entry
|
||
|
prv_lookup("", db, &header_len, header, HEADER_BUFFER_SIZE);
|
||
|
if (!header_len) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Could not find header in language pack");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
// Isolate the language substring
|
||
|
if (!prv_get_property(header, "Language: ", db->iso_locale, ISO_LOCALE_LENGTH)) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Could not parse a language from language pack");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
// Isolate the language name
|
||
|
if (!prv_get_property(header, "Name: ", db->lang_name, LOCALE_NAME_LENGTH)) {
|
||
|
strcpy(db->lang_name, "Unknown");
|
||
|
}
|
||
|
|
||
|
// Isolate the version value
|
||
|
char version_str[10] = {0};
|
||
|
if (!prv_get_property(header, "Project-Id-Version: ", version_str, 10)) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Could not parse a version from language pack");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
char *version_end;
|
||
|
db->lang_version = strtol(version_str, &version_end, 0);
|
||
|
if (version_end == version_str) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Could not parse a version from language pack");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
success = true;
|
||
|
PBL_LOG(LOG_LEVEL_INFO, "language: %s, version %d", db->iso_locale, db->lang_version);
|
||
|
|
||
|
cleanup:
|
||
|
kernel_free(header);
|
||
|
return (success);
|
||
|
}
|
||
|
|
||
|
static int prv_unmapit(struct DomainBinding *db) {
|
||
|
MoHandle *mohandle = &db->mohandle;
|
||
|
|
||
|
kernel_free(mohandle->mo.mo_htable);
|
||
|
mohandle->mo.mo_htable = NULL;
|
||
|
mohandle->mo = (Mo){};
|
||
|
strcpy(db->iso_locale, "en_US");
|
||
|
strcpy(db->lang_name, "English");
|
||
|
db->lang_version = 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static bool prv_mapit(const uint32_t resource_id, struct DomainBinding *db) {
|
||
|
// If the resource is changed on disk, our resource_watch callback will set need_reload
|
||
|
if (!db->need_reload) {
|
||
|
return (db->version.crc != 0);
|
||
|
}
|
||
|
|
||
|
PBL_LOG(LOG_LEVEL_DEBUG, "New language detected!");
|
||
|
db->need_reload = false;
|
||
|
|
||
|
/* save version */
|
||
|
db->version = resource_get_version(SYSTEM_APP, resource_id);
|
||
|
prv_list_flush();
|
||
|
prv_unmapit(db);
|
||
|
|
||
|
unsigned int size;
|
||
|
if ((size = resource_size(SYSTEM_APP, resource_id)) < sizeof(MoHeader)) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (!resource_is_valid(SYSTEM_APP, resource_id)) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
MoHandle *mohandle = &db->mohandle;
|
||
|
if (resource_load_byte_range_system(SYSTEM_APP, resource_id, 0,
|
||
|
(uint8_t *)&mohandle->mo.hdr, sizeof(MoHeader)) == 0) {
|
||
|
goto fail;
|
||
|
}
|
||
|
if (mohandle->mo.hdr.mo_magic != MO_MAGIC) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
mohandle->len = size;
|
||
|
/* validate htable */
|
||
|
if (mohandle->mo.hdr.mo_hsize < 2) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
size_t htable_size = sizeof(uint32_t) * mohandle->mo.hdr.mo_hsize;
|
||
|
uint32_t *htable = kernel_malloc_check(htable_size);
|
||
|
mohandle->mo.mo_htable = htable;
|
||
|
if (resource_load_byte_range_system(SYSTEM_APP, resource_id, mohandle->mo.hdr.mo_hoffset,
|
||
|
(uint8_t *)htable, htable_size) == 0) {
|
||
|
prv_unmapit(db);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
for (unsigned int i = 0; i < mohandle->mo.hdr.mo_hsize; ++i) {
|
||
|
if (htable[i] > mohandle->mo.hdr.mo_nstring) {
|
||
|
/* illegal string number */
|
||
|
prv_unmapit(db);
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!prv_get_metadata(db)) {
|
||
|
prv_unmapit(db);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
|
||
|
fail:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
// Strings List Manipulation
|
||
|
|
||
|
void prv_list_flush(void) {
|
||
|
ListNode *cur = (ListNode *)s_system_domain.strings_list;
|
||
|
while (cur) {
|
||
|
ListNode *next = list_get_next(cur);
|
||
|
kernel_free(cur);
|
||
|
cur = next;
|
||
|
}
|
||
|
s_system_domain.strings_list = NULL;
|
||
|
}
|
||
|
|
||
|
static bool prv_list_string_filter_callback(ListNode *found_node, void *data) {
|
||
|
I18nString *i18n_string = (I18nString *)found_node;
|
||
|
StringLookupInfo *lookup_info = data;
|
||
|
if (i18n_string->original_hash == lookup_info->hash &&
|
||
|
lookup_info->owner == i18n_string->owner &&
|
||
|
strcmp(i18n_string->original_string, lookup_info->string) == 0) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool prv_list_owner_filter_callback(ListNode *found_node, void *owner) {
|
||
|
I18nString *i18n_string = (I18nString *)found_node;
|
||
|
if (i18n_string->owner == owner) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Not static because we call this from unit test code
|
||
|
I18nString *prv_list_find_string(const char *string, const void *owner) {
|
||
|
StringLookupInfo lookup_info = {
|
||
|
.string = string,
|
||
|
.hash = prv_gettext_hash(string),
|
||
|
.owner = owner
|
||
|
};
|
||
|
return (I18nString *)list_find((ListNode *)s_system_domain.strings_list,
|
||
|
prv_list_string_filter_callback, (void *)&lookup_info);
|
||
|
}
|
||
|
|
||
|
|
||
|
static const char *prv_list_add_string(const char *original_string, const char *translated_string,
|
||
|
const void *owner) {
|
||
|
uint32_t translated_len = strlen(translated_string);
|
||
|
|
||
|
// Allocate enough space to hold the original and translated strings. The translated string
|
||
|
// is stored at i18n_string->translated and the original string immediately after that.
|
||
|
I18nString *i18n_string = kernel_malloc_check(sizeof(I18nString) + translated_len + 1
|
||
|
+ strlen(original_string) + 1);
|
||
|
|
||
|
list_init(&i18n_string->node);
|
||
|
i18n_string->owner = owner;
|
||
|
|
||
|
strcpy(i18n_string->translated_string, translated_string);
|
||
|
|
||
|
i18n_string->original_hash = prv_gettext_hash(original_string);
|
||
|
// Store the original string immediately after the translated one in memory.
|
||
|
i18n_string->original_string = &i18n_string->translated_string[translated_len + 1];
|
||
|
strcpy(i18n_string->original_string, original_string);
|
||
|
|
||
|
I18nString **strings_list = &s_system_domain.strings_list;
|
||
|
*strings_list = (I18nString *)list_prepend((ListNode *)*strings_list, &i18n_string->node);
|
||
|
|
||
|
if (translated_len > 0) {
|
||
|
return (i18n_string->translated_string);
|
||
|
} else {
|
||
|
return original_string;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void prv_list_remove_string(I18nString *i18n_string) {
|
||
|
list_remove(&i18n_string->node, (ListNode **)&s_system_domain.strings_list, NULL);
|
||
|
kernel_free(i18n_string);
|
||
|
}
|
||
|
|
||
|
static bool prv_check_domain(struct DomainBinding *db) {
|
||
|
return (prv_mapit(s_system_domain.resource_id, db));
|
||
|
}
|
||
|
|
||
|
static const char *prv_message_from_msgid(const char *msgid) {
|
||
|
// If a string wasn't found, we want to return the original string.
|
||
|
// However, if we have a context, this string needs to not show the context.
|
||
|
// So we just find EOT and if it's present return the next character.
|
||
|
const char *message = strchr(msgid, '\4');
|
||
|
if (message == NULL) {
|
||
|
// No context, the whole string is the message.
|
||
|
return msgid;
|
||
|
}
|
||
|
// strchr gets the address of that character. We want to skip the EOT, so +1.
|
||
|
return message + 1;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
// i18n API
|
||
|
|
||
|
// NOTE: Currently, we don't do reference counting, so bad things will happen if the caller
|
||
|
// calls i18n_get() on the same string more than once and assumes that any of those return
|
||
|
// pointers will still be valid after i18n_free() is called on one of them.
|
||
|
const char *i18n_get(const char *msgid, const void *owner) {
|
||
|
PBL_ASSERTN(owner);
|
||
|
if (msgid == NULL || msgid[0] == 0) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
struct DomainBinding *db = &s_system_domain;
|
||
|
if (!prv_check_domain(db)) {
|
||
|
goto fail;
|
||
|
}
|
||
|
// See if this original has been cached.
|
||
|
I18nString *i18n_string = prv_list_find_string(msgid, owner);
|
||
|
if (i18n_string) {
|
||
|
if (i18n_string->translated_string[0]) {
|
||
|
return i18n_string->translated_string;
|
||
|
} else {
|
||
|
// No translation exists for this string, return original
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Lookup the translation from the language pack and add it to our cache
|
||
|
char translated[200];
|
||
|
size_t len = 0;
|
||
|
prv_lookup(msgid, db, &len, translated, sizeof(translated));
|
||
|
if (len >= sizeof(translated)) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Truncated string: <%s>", msgid);
|
||
|
}
|
||
|
|
||
|
if (len) {
|
||
|
return prv_list_add_string(msgid, translated, owner);
|
||
|
} else {
|
||
|
// Add to cache as an untranslatable string so we don't waste time looking for it again.
|
||
|
prv_list_add_string(msgid, (const char *)"", owner);
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
// String not found or an error occurred.
|
||
|
return prv_message_from_msgid(msgid);
|
||
|
}
|
||
|
|
||
|
void i18n_get_with_buffer(const char *msgid, char *buffer, size_t length) {
|
||
|
if (msgid == NULL || msgid[0] == 0) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
struct DomainBinding *db = &s_system_domain;
|
||
|
if (!prv_check_domain(db)) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
size_t len = 0;
|
||
|
prv_lookup(msgid, db, &len, buffer, length);
|
||
|
if (len >= length) {
|
||
|
PBL_LOG(LOG_LEVEL_WARNING, "Truncated string: <%s>", msgid);
|
||
|
}
|
||
|
|
||
|
if (len) {
|
||
|
// buffer has been written, return
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
msgid = prv_message_from_msgid(msgid);
|
||
|
strncpy(buffer, msgid, length);
|
||
|
buffer[length - 1] = '\0';
|
||
|
}
|
||
|
|
||
|
size_t i18n_get_length(const char *msgid) {
|
||
|
if (msgid == NULL || msgid[0] == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
struct DomainBinding *db = &s_system_domain;
|
||
|
if (!prv_check_domain(db)) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
size_t len = 0;
|
||
|
prv_lookup(msgid, db, &len, NULL, 0);
|
||
|
if (len) { // String was found
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
// String not found, or error occurred
|
||
|
msgid = prv_message_from_msgid(msgid);
|
||
|
return strlen(msgid);
|
||
|
}
|
||
|
|
||
|
void i18n_free(const char *original, const void *owner) {
|
||
|
PBL_ASSERTN(owner);
|
||
|
I18nString *i18n_string = prv_list_find_string(original, owner);
|
||
|
if (i18n_string) {
|
||
|
prv_list_remove_string(i18n_string);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void i18n_free_all(const void *owner) {
|
||
|
I18nString *cur_string = (I18nString *)list_find((ListNode *)s_system_domain.strings_list,
|
||
|
prv_list_owner_filter_callback, (void*)owner);
|
||
|
while (cur_string) {
|
||
|
I18nString *next_string = (I18nString *)list_find_next(&cur_string->node,
|
||
|
prv_list_owner_filter_callback, false, (void*)owner);
|
||
|
prv_list_remove_string(cur_string);
|
||
|
cur_string = next_string;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void prv_resource_changed_handler(void *data) {
|
||
|
struct DomainBinding *db = (struct DomainBinding *)data;
|
||
|
// Mark as invalid
|
||
|
PBL_LOG(LOG_LEVEL_DEBUG, "lang resource file reloading");
|
||
|
shell_prefs_set_language_english(false);
|
||
|
db->need_reload = true;
|
||
|
|
||
|
if (resource_is_valid(SYSTEM_APP, db->resource_id)) {
|
||
|
language_ui_display_changed(db->lang_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void prv_resource_changed_callback(void *data) {
|
||
|
// We want to not actually handle the reload here, because the PFS lock is still held here.
|
||
|
// So instead we throw in the reload as an event callback.
|
||
|
PBL_LOG(LOG_LEVEL_DEBUG, "lang resource file was modified");
|
||
|
launcher_task_add_callback(prv_resource_changed_handler, data);
|
||
|
}
|
||
|
|
||
|
static void prv_unset(void) {
|
||
|
s_system_domain.need_reload = false;
|
||
|
prv_list_flush();
|
||
|
prv_unmapit(&s_system_domain);
|
||
|
}
|
||
|
|
||
|
void i18n_set_resource(uint32_t resource_id) {
|
||
|
// Remove prior watch, if any
|
||
|
// Warning: you better be sure we're not calling from the resource changed callback.
|
||
|
if (s_system_domain.watch_handle) {
|
||
|
resource_unwatch(s_system_domain.watch_handle);
|
||
|
}
|
||
|
|
||
|
s_system_domain.resource_id = resource_id;
|
||
|
s_system_domain.watch_handle = resource_watch(SYSTEM_APP, resource_id,
|
||
|
prv_resource_changed_callback, &s_system_domain);
|
||
|
|
||
|
if (shell_prefs_get_language_english()) {
|
||
|
prv_unset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
s_system_domain.need_reload = true;
|
||
|
|
||
|
// try mapping it right away
|
||
|
prv_mapit(resource_id, &s_system_domain);
|
||
|
}
|
||
|
|
||
|
char *i18n_get_locale(void) {
|
||
|
return (s_system_domain.iso_locale);
|
||
|
}
|
||
|
|
||
|
uint16_t i18n_get_version(void) {
|
||
|
return (s_system_domain.lang_version);
|
||
|
}
|
||
|
|
||
|
char *i18n_get_lang_name(void) {
|
||
|
return (s_system_domain.lang_name);
|
||
|
}
|
||
|
|
||
|
void i18n_enable(bool enable) {
|
||
|
if (enable) {
|
||
|
s_system_domain.need_reload = true;
|
||
|
prv_mapit(s_system_domain.resource_id, &s_system_domain);
|
||
|
} else {
|
||
|
prv_unset();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void command_i18n_resource(const char *arg) {
|
||
|
uint32_t resource_id = atoi(arg);
|
||
|
i18n_set_resource(resource_id);
|
||
|
}
|
||
|
|