using UnityEditor; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Tables; using UnityEditor.Localization; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; public class LocalizationMultiTableImporter { [MenuItem("Localization/Import All Tables from CSV")] public static void ImportAllLocalizationTables() { string path = EditorUtility.OpenFilePanel("Import Localization CSV", "", "csv"); if (string.IsNullOrEmpty(path)) return; string rawCsv = File.ReadAllText(path, Encoding.UTF8); var lines = SplitCSVRows(rawCsv); if (lines.Count < 2) { Debug.LogError("CSV file is empty or missing header."); return; } // Header: Table,Key,locale1,locale2,... var header = SplitCSVLine(lines[0]); if (header.Count < 3) { Debug.LogError("CSV header must contain at least 'Table,Key,'"); return; } var localeCodes = header.Skip(2).ToList(); var locales = LocalizationEditorSettings.GetLocales(); var localeMap = locales.ToDictionary(l => l.Identifier.Code, l => l); var collections = LocalizationEditorSettings.GetStringTableCollections(); for (int lineIndex = 1; lineIndex < lines.Count; lineIndex++) { var line = lines[lineIndex]; var cells = SplitCSVLine(line); if (cells.Count < 2) continue; string tableName = cells[0]; string key = cells[1]; var collection = collections.FirstOrDefault(c => c.TableCollectionName == tableName); if (collection == null) { Debug.LogWarning($"Skipping unknown table: {tableName}"); continue; } for (int i = 0; i < localeCodes.Count; i++) { string localeCode = localeCodes[i]; if (!localeMap.TryGetValue(localeCode, out var locale)) continue; var table = collection.GetTable(locale.Identifier) as StringTable; if (table == null) { table = collection.AddNewTable(locale.Identifier) as StringTable; } var sharedData = collection.SharedData; var sharedEntry = sharedData.GetEntry(key); if (sharedEntry == null) { sharedEntry = sharedData.AddKey(key); } var entry = table.GetEntry(sharedEntry.Id); if (entry == null) { entry = table.AddEntry(sharedEntry.Id, ""); } if (i + 2 < cells.Count) { entry.Value = cells[i + 2]; } EditorUtility.SetDirty(table); } } AssetDatabase.SaveAssets(); Debug.Log(" Localization import complete!"); } /// /// Splits CSV into rows, handling multiline quoted entries. /// private static List SplitCSVRows(string csvText) { var rows = new List(); var sb = new StringBuilder(); bool inQuotes = false; for (int i = 0; i < csvText.Length; i++) { char c = csvText[i]; sb.Append(c); if (c == '"') { // Check for escaped quote if (i + 1 < csvText.Length && csvText[i + 1] == '"') { sb.Append('"'); i++; } else { inQuotes = !inQuotes; } } else if ((c == '\n' || c == '\r') && !inQuotes) { // Handle Windows \r\n if (c == '\r' && i + 1 < csvText.Length && csvText[i + 1] == '\n') { sb.Append('\n'); i++; } rows.Add(sb.ToString().TrimEnd('\r', '\n')); sb.Clear(); } } if (sb.Length > 0) rows.Add(sb.ToString().TrimEnd('\r', '\n')); return rows; } /// /// Splits a CSV line into fields, handling commas and quotes correctly. /// private static List SplitCSVLine(string line) { var result = new List(); var sb = new StringBuilder(); bool inQuotes = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (inQuotes) { if (c == '"') { if (i + 1 < line.Length && line[i + 1] == '"') { sb.Append('"'); // escaped quote i++; } else { inQuotes = false; } } else { sb.Append(c); } } else { if (c == '"') { inQuotes = true; } else if (c == ',') { result.Add(sb.ToString()); sb.Clear(); } else { sb.Append(c); } } } result.Add(sb.ToString()); return result; } }