Hi,
I am currently trying to create a runtime translation updater in my application based on a CSV file downloaded from the server.
The CSV file is generated on the server and has identical structure to that exported in the CSV Extension available from the String Table Collections object.
I tried to use the script suggested by you but I totally don’t know how to initialize it and upload CSV file - I suspect I need to run Provide (ProvideHandle provideHandle) but I don’t know how to create ProvideHandle object with information about CSV file. In addition, it seems to me that the above script is used to add a new Locale with values.
I will definitely need it in the future, but at the moment I am looking for a solution to update and save translations in the runtime.
I used and tweaked a little bit the above class for CSV interpretation and creation of temp string table and wrote something like this:
public IEnumerator UpdateTranslations(string _translationsCSVFilePath){
// save starting locale
Locale _baseLocale = LocalizationSettings.SelectedLocale;
// loop throu all available locales
foreach(Locale _locale in LocalizationSettings.AvailableLocales.Locales){
LocalizationSettings.SelectedLocale = _locale;
// get localized string table database based on active locale
AsyncOperationHandle<StringTable> _asyncStringTableDatabase = LocalizationSettings.StringDatabase.GetTableAsync(LocalizedStringTables.TableReference);
yield return new WaitUntil(()=> _asyncStringTableDatabase.IsDone);
StringTable _stDatabase = _asyncStringTableDatabase.Result;
// create string table corresponding to active locale (CSV contains cols from all locales).
// The safest way to get propper CSV structure is to use export CSV extension from your primary String Table Collection object in Unity inspector
StringTable _importedST = ImportTable(_translationsCSVFilePath, _locale);
// this part finds and adds fields that are empty in selected language string database but exist in shared values and in imported StringTable.
// If fields in database are empty they can't be updated - _stDatabase.Values.Count = number of filled fields
// further more it will add new row in Localization Tables if the key is unique - it desn't exist
foreach(StringTableEntry _value in _importedST.Values){
if(_value.Value != "" && _value.Value != null){
if(_stDatabase.SharedData.Contains(_value.KeyId)){
if(!_stDatabase.ContainsKey(_value.KeyId)){
_stDatabase.AddEntry(_value.Key, _value.Value);
}
}
}
}
// this part updates the editor runtime string database values - will be visable in runtime. Also the Window > Asset Management > Localization Talbes, will also be updated but after you stop and run the app again from the editor
foreach(StringTableEntry _value in _stDatabase.Values){
if(_importedST[_value.Key] == null){ continue;} // for safety - skip that translation part if key from database doesnt exist in imported csv
string _val = _importedST[_value.Key].Value;
if(_value.Value != _val){
_value.Value = _val;
}
}
// get addressable string tables based on active locale. Just pulling the values will update displayed values on the device
AsyncOperationHandle<StringTable> _asyncAddressablesTable = Addressables.LoadAssetAsync<StringTable>("Locale-" + _locale.Identifier.Code);
yield return new WaitUntil(()=> _asyncAddressablesTable.IsDone);
StringTable _addressablesTable = _asyncAddressablesTable.Result;
// This script will work only if in "Window > Asset Managmenet > Addressables > Groups" window, the "Simulate Groups (advanced)" option located in the "Play Mode Script" dropdown will be selected.
}
// switch to saved starting locale
LocalizationSettings.SelectedLocale = _baseLocale;
AppManager.SetBaseMessages();
}
private StringTable ImportTable(string _filePath, Locale _locale)
{
// Locale newLocale = Locale.CreateLocale(Path.GetFileNameWithoutExtension(_filePath));
List<(string _key, string _value)> _entries = ReadCsvEntries(_filePath, _locale.LocaleName);
StringTable _stringTable = ScriptableObject.CreateInstance<StringTable>();
_stringTable.LocaleIdentifier = _locale.Identifier;
_stringTable.SharedData = SharedTable;
for (int i = 0; i < _entries.Count; i++)
{
_stringTable.AddEntry(
_entries[i]._key,
_entries[i]._value);
}
return _stringTable;
}
private List<(string, string)> ReadCsvEntries(string _filePath, string _localeName)
{
List<(string, string)> _entries = new List<(string, string)>();
DataTable _csvTable = new DataTable();
using (var _stream = new StreamReader(_filePath))
{
using (var _csv = new CsvReader(_stream, CultureInfo.InvariantCulture))
{
_csv.Read();
_csv.ReadHeader();
while (_csv.Read())
{
_entries.Add((
_csv.GetField("Key"),
_csv.GetField(_localeName.Replace(") (", ")("))));
// it mightr be a bug but the Locale identifier and he header in exported CSV file corresponding to the language differs - theres extra space between brackets
// standard CSV Extension export -> English (United Kingdom)(en-GB), Locale English (United Kingdom) (en-GB)
// the CSV names can be changed in CSV Extension, but keep in mind that the standard extension removes this space (I didn't notice it at first)
}
}
}
return _entries;
}
as you will notice the CSV file is used to create temporary StringTable instances with the Key, and value columns coresponding to the currently selected Locale. Then a few parts that update the values according to the created table - the values displayed in the editor player, the stringtables database (which is useful so that you do not have to do it manually each time) and AddressableTables, which I care about the most, i.e. changing the values on users’ devices.
However, I have a problem - updating values on users’ devices only works if we run the above script. After restarting the application, the values return to their original values.
I suspect it has something to do with updating the Catalogs, but when I run a script that I found on another post:
private IEnumerator UpdateCatalogs()
{
List<string> catalogsToUpdate = new List<string>();
AsyncOperationHandle<List<string>> checkForUpdateHandle = Addressables.CheckForCatalogUpdates();
checkForUpdateHandle.Completed += op =>
{
Debug.Log(op.Result.Count);
catalogsToUpdate.AddRange(op.Result);
};
yield return checkForUpdateHandle;
if (catalogsToUpdate.Count > 0)
{
AsyncOperationHandle<List<IResourceLocator>> updateHandle = Addressables.UpdateCatalogs(catalogsToUpdate);
yield return updateHandle;
}
}
…it does not find any values to update.
What am I doing wrong? How can I save modified values and StringTables in runtime on the device (not only in editor player runtime) so the next time i call Localized string i’ll get the updated values?