Skip to content

Commit f8496ab

Browse files
authored
Merge pull request #17 from Flow-Launcher/format_string
Fix Index Issue for Format String & Add Culture Info When Need to Format
2 parents 968c1a5 + 671fc75 commit f8496ab

File tree

1 file changed

+189
-59
lines changed

1 file changed

+189
-59
lines changed

Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs

Lines changed: 189 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -192,22 +192,166 @@ private static ImmutableArray<LocalizableString> ParseXamlFile(AdditionalText fi
192192

193193
if (key != null)
194194
{
195-
localizableStrings.Add(ParseLocalizableString(key, value, comment));
195+
var formatParams = GetParameters(value);
196+
var (summary, updatedFormatParams) = ParseCommentAndUpdateParameters(comment, formatParams);
197+
localizableStrings.Add(new LocalizableString(key, value, summary, updatedFormatParams));
196198
}
197199
}
198200

199201
return localizableStrings.ToImmutableArray();
200202
}
201203

202-
private static LocalizableString ParseLocalizableString(string key, string value, XComment comment)
204+
/// <summary>
205+
/// Analyzes the format string and returns a list of its parameters.
206+
/// </summary>
207+
/// <param name="format"></param>
208+
/// <returns></returns>
209+
private static List<LocalizableStringParam> GetParameters(string format)
203210
{
204-
var (summary, parameters) = ParseComment(comment);
205-
return new LocalizableString(key, value, summary, parameters);
211+
var parameters = new Dictionary<int, string>();
212+
int maxIndex = -1;
213+
int i = 0;
214+
int len = format.Length;
215+
216+
while (i < len)
217+
{
218+
if (format[i] == '{')
219+
{
220+
if (i + 1 < len && format[i + 1] == '{')
221+
{
222+
// Escaped '{', skip both
223+
i += 2;
224+
continue;
225+
}
226+
else
227+
{
228+
// Start of a format item, parse index and format
229+
i++; // Move past '{'
230+
int index = 0;
231+
bool hasIndex = false;
232+
233+
// Parse index
234+
while (i < len && char.IsDigit(format[i]))
235+
{
236+
hasIndex = true;
237+
index = index * 10 + (format[i] - '0');
238+
i++;
239+
}
240+
241+
if (!hasIndex)
242+
{
243+
// Skip invalid format item
244+
while (i < len && format[i] != '}')
245+
{
246+
i++;
247+
}
248+
if (i < len)
249+
{
250+
i++; // Move past '}'
251+
}
252+
continue;
253+
}
254+
255+
// Check for alignment (comma followed by optional sign and digits)
256+
if (i < len && format[i] == ',')
257+
{
258+
i++; // Skip comma and optional sign
259+
if (i < len && (format[i] == '+' || format[i] == '-'))
260+
{
261+
i++;
262+
}
263+
// Skip digits
264+
while (i < len && char.IsDigit(format[i]))
265+
{
266+
i++;
267+
}
268+
}
269+
270+
string formatPart = null;
271+
272+
// Check for format (after colon)
273+
if (i < len && format[i] == ':')
274+
{
275+
i++; // Move past ':'
276+
int start = i;
277+
while (i < len && format[i] != '}')
278+
{
279+
i++;
280+
}
281+
formatPart = i < len ? format.Substring(start, i - start) : format.Substring(start);
282+
if (i < len)
283+
{
284+
i++; // Move past '}'
285+
}
286+
}
287+
else if (i < len && format[i] == '}')
288+
{
289+
// No format part
290+
i++; // Move past '}'
291+
}
292+
else
293+
{
294+
// Invalid characters after index, skip to '}'
295+
while (i < len && format[i] != '}')
296+
{
297+
i++;
298+
}
299+
if (i < len)
300+
{
301+
i++; // Move past '}'
302+
}
303+
}
304+
305+
parameters[index] = formatPart;
306+
if (index > maxIndex)
307+
{
308+
maxIndex = index;
309+
}
310+
}
311+
}
312+
else if (format[i] == '}')
313+
{
314+
// Handle possible escaped '}}'
315+
if (i + 1 < len && format[i + 1] == '}')
316+
{
317+
i += 2; // Skip escaped '}}'
318+
}
319+
else
320+
{
321+
i++; // Move past '}'
322+
}
323+
}
324+
else
325+
{
326+
i++;
327+
}
328+
}
329+
330+
// Generate the result list from 0 to maxIndex
331+
var result = new List<LocalizableStringParam>();
332+
if (maxIndex == -1)
333+
{
334+
return result;
335+
}
336+
337+
for (int idx = 0; idx <= maxIndex; idx++)
338+
{
339+
var formatValue = parameters.TryGetValue(idx, out var value) ? value : null;
340+
result.Add(new LocalizableStringParam { Index = idx, Format = formatValue, Name = $"arg{idx}", Type = "object?" });
341+
}
342+
343+
return result;
206344
}
207345

208-
private static (string Summary, ImmutableArray<LocalizableStringParam> Parameters) ParseComment(XComment comment)
346+
/// <summary>
347+
/// Parses the comment and updates the format parameter names and types.
348+
/// </summary>
349+
/// <param name="comment"></param>
350+
/// <param name="parameters"></param>
351+
/// <returns></returns>
352+
private static (string Summary, ImmutableArray<LocalizableStringParam> Parameters) ParseCommentAndUpdateParameters(XComment comment, List<LocalizableStringParam> parameters)
209353
{
210-
if (comment == null || comment.Value == null)
354+
if (comment == null || comment.Value == null || parameters.Count == 0)
211355
{
212356
return (null, _emptyLocalizableStringParams);
213357
}
@@ -216,13 +360,26 @@ private static (string Summary, ImmutableArray<LocalizableStringParam> Parameter
216360
{
217361
var doc = XDocument.Parse($"<root>{comment.Value}</root>");
218362
var summary = doc.Descendants("summary").FirstOrDefault()?.Value.Trim();
219-
var parameters = doc.Descendants("param")
220-
.Select(p => new LocalizableStringParam(
221-
int.Parse(p.Attribute("index").Value),
222-
p.Attribute("name").Value,
223-
p.Attribute("type").Value))
224-
.ToImmutableArray();
225-
return (summary, parameters);
363+
364+
// Update parameter names and types of the format string
365+
foreach (var p in doc.Descendants("param"))
366+
{
367+
var index = int.TryParse(p.Attribute("index").Value, out var intValue) ? intValue : -1;
368+
var name = p.Attribute("name").Value;
369+
var type = p.Attribute("type").Value;
370+
if (index >= 0 && index < parameters.Count)
371+
{
372+
if (!string.IsNullOrEmpty(name))
373+
{
374+
parameters[index].Name = name;
375+
}
376+
if (!string.IsNullOrEmpty(type))
377+
{
378+
parameters[index].Type = type;
379+
}
380+
}
381+
}
382+
return (summary, parameters.ToImmutableArray());
226383
}
227384
catch
228385
{
@@ -521,25 +678,32 @@ private static void GenerateLocalizationMethod(
521678
string tabString)
522679
{
523680
sb.Append($"{tabString}public static string {ls.Key}(");
524-
var parameters = BuildParameters(ls);
681+
682+
// Get parameter string
683+
var parameters = ls.Params.ToList();
525684
sb.Append(string.Join(", ", parameters.Select(p => $"{p.Type} {p.Name}")));
526685
sb.Append(") => ");
527-
528686
var formatArgs = parameters.Count > 0
529687
? $", {string.Join(", ", parameters.Select(p => p.Name))}"
530688
: string.Empty;
531689

532690
if (isCoreAssembly)
533691
{
692+
var getTranslation = "InternationalizationManager.Instance.GetTranslation";
534693
sb.AppendLine(parameters.Count > 0
535-
? $"string.Format(InternationalizationManager.Instance.GetTranslation(\"{ls.Key}\"){formatArgs});"
536-
: $"InternationalizationManager.Instance.GetTranslation(\"{ls.Key}\");");
694+
? !ls.Format ?
695+
$"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});"
696+
: $"string.Format(System.Globalization.CultureInfo.CurrentCulture, {getTranslation}(\"{ls.Key}\"){formatArgs});"
697+
: $"{getTranslation}(\"{ls.Key}\");");
537698
}
538699
else if (pluginInfo?.IsValid == true)
539700
{
701+
var getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation";
540702
sb.AppendLine(parameters.Count > 0
541-
? $"string.Format({pluginInfo.ContextAccessor}.API.GetTranslation(\"{ls.Key}\"){formatArgs});"
542-
: $"{pluginInfo.ContextAccessor}.API.GetTranslation(\"{ls.Key}\");");
703+
? !ls.Format ?
704+
$"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});"
705+
: $"string.Format(System.Globalization.CultureInfo.CurrentCulture, {getTranslation}(\"{ls.Key}\"){formatArgs});"
706+
: $"{getTranslation}(\"{ls.Key}\");");
543707
}
544708
else
545709
{
@@ -549,24 +713,6 @@ private static void GenerateLocalizationMethod(
549713
sb.AppendLine();
550714
}
551715

552-
private static List<MethodParameter> BuildParameters(LocalizableString ls)
553-
{
554-
var parameters = new List<MethodParameter>();
555-
for (var i = 0; i < 10; i++)
556-
{
557-
if (!ls.Value.Contains($"{{{i}}}"))
558-
{
559-
continue;
560-
}
561-
562-
var param = ls.Params.FirstOrDefault(p => p.Index == i);
563-
parameters.Add(param is null
564-
? new MethodParameter($"arg{i}", "object?")
565-
: new MethodParameter(param.Name, param.Type));
566-
}
567-
return parameters;
568-
}
569-
570716
private static string Spacing(int n)
571717
{
572718
Span<char> spaces = stackalloc char[n * 4];
@@ -585,30 +731,12 @@ private static string Spacing(int n)
585731

586732
#region Classes
587733

588-
public class MethodParameter
589-
{
590-
public string Name { get; }
591-
public string Type { get; }
592-
593-
public MethodParameter(string name, string type)
594-
{
595-
Name = name;
596-
Type = type;
597-
}
598-
}
599-
600734
public class LocalizableStringParam
601735
{
602-
public int Index { get; }
603-
public string Name { get; }
604-
public string Type { get; }
605-
606-
public LocalizableStringParam(int index, string name, string type)
607-
{
608-
Index = index;
609-
Name = name;
610-
Type = type;
611-
}
736+
public int Index { get; set; }
737+
public string Format { get; set; }
738+
public string Name { get; set; }
739+
public string Type { get; set; }
612740
}
613741

614742
public class LocalizableString
@@ -617,6 +745,8 @@ public class LocalizableString
617745
public string Value { get; }
618746
public string Summary { get; }
619747
public IEnumerable<LocalizableStringParam> Params { get; }
748+
749+
public bool Format => Params.Any(p => !string.IsNullOrEmpty(p.Format));
620750

621751
public LocalizableString(string key, string value, string summary, IEnumerable<LocalizableStringParam> @params)
622752
{

0 commit comments

Comments
 (0)