using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using LocalizationTool.Models; namespace LocalizationTool.Services { /// /// CSV 导入导出(逗号分隔,Excel 可直接打开编辑)。 /// 列结构与主界面表格一致:第一列"中文原文",其后为各语言代码列(en-US、ja-JP…)。 /// 导出 UTF-8 带 BOM(Excel 中文不乱码);导入兼容 UTF-8 与 ANSI(GB18030)。 /// public static class CsvExchange { /// 导出表格行到 CSV 文件。langs 决定语言列(与表格当前勾选列一致)。 public static void Export(string path, IEnumerable rows, List langs) { var sb = new StringBuilder(); sb.AppendLine(JoinCsv(new[] { "中文原文" }.Concat(langs.Select(l => l.LanguageCode)))); foreach (var r in rows) { var fields = new List { r.Key }; foreach (var l in langs) fields.Add(r.Cells.TryGetValue(l.LanguageCode, out var c) ? c.Value : ""); sb.AppendLine(JoinCsv(fields)); } File.WriteAllText(path, sb.ToString(), new UTF8Encoding(true)); // BOM:Excel 识别 UTF-8 } /// 解析 CSV 文件:返回 (表头, 数据行)。空行跳过;引号/逗号/换行转义按 RFC4180 处理。 public static Tuple, List> Parse(string path) { var bytes = File.ReadAllBytes(path); if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) bytes = bytes.Skip(3).ToArray(); string text; try { text = new UTF8Encoding(false, true).GetString(bytes); } catch { text = Encoding.GetEncoding("gb18030").GetString(bytes); } // Excel 另存的 ANSI CSV var header = new List(); var records = new List(); using (var reader = new StringReader(text)) using (var parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(reader)) { parser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited; parser.SetDelimiters(","); parser.HasFieldsEnclosedInQuotes = true; while (!parser.EndOfData) { string[] fields; try { fields = parser.ReadFields(); } catch (Microsoft.VisualBasic.FileIO.MalformedLineException) { throw new Exception($"第 {parser.ErrorLineNumber} 行格式错误(常见原因:引号未闭合)"); } if (fields == null) continue; if (fields.All(string.IsNullOrWhiteSpace)) continue; if (header.Count == 0) { header.AddRange(fields); continue; } records.Add(fields); } } return Tuple.Create(header, records); } private static string JoinCsv(IEnumerable fields) => string.Join(",", fields.Select(Escape)); private static string Escape(string field) { field = field ?? ""; if (field.IndexOfAny(new[] { ',', '"', '\r', '\n' }) >= 0) return "\"" + field.Replace("\"", "\"\"") + "\""; return field; } } }