Skip to content

Commit 0318b65

Browse files
committed
release: promote 0.2.0 product source
1 parent 39b29ad commit 0318b65

93 files changed

Lines changed: 6137 additions & 924 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,30 @@ jobs:
4646
throw "RELEASE_NOTES.md is missing"
4747
}
4848
49-
- name: Build product
49+
- name: Resolve product version
5050
shell: pwsh
51-
run: dotnet build .\OpenVisionLab.LabelingStudio.csproj -c Release
51+
run: |
52+
[xml]$props = Get-Content Directory.Build.props -Raw
53+
$version = [string]$props.Project.PropertyGroup.VersionPrefix
54+
if ($version -notmatch '^\d+\.\d+\.\d+$') {
55+
throw "Invalid product version: $version"
56+
}
57+
"PRODUCT_VERSION=$version" >> $env:GITHUB_ENV
5258
53-
- name: Publish versioned self-contained release
59+
- name: Build application
5460
shell: pwsh
55-
run: powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\publish-win-x64.ps1 -Configuration Release
61+
run: dotnet build .\OpenVisionLab.LabelingStudio.csproj -c Release -p:Platform=x64 -m:1 -nr:false /p:UseSharedCompilation=false
5662

57-
- name: Verify release package payload
63+
- name: Publish versioned self-contained release
5864
shell: pwsh
59-
run: powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\publish-win-x64.ps1 -Configuration Release -ReleaseVersion 0.1.0 -VerifyOnly
65+
run: powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\publish-win-x64.ps1 -Configuration Release
6066

6167
- name: Upload versioned win-x64 release
6268
if: github.event_name != 'pull_request'
6369
uses: actions/upload-artifact@v4
6470
with:
65-
name: openvisionlab-labeling-studio-0.1.0-win-x64
66-
path: artifacts/publish/Release/win-x64/0.1.0
71+
name: openvisionlab-labeling-studio-${{ env.PRODUCT_VERSION }}-win-x64
72+
path: artifacts/publish/Release/win-x64/${{ env.PRODUCT_VERSION }}
6773
if-no-files-found: error
6874
retention-days: 1
6975

0. UI/9) WPF/Models/WpfImageQueueModels.cs

Lines changed: 212 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using MahApps.Metro.IconPacks;
22
using MvcVisionSystem.Yolo;
3+
using OpenVisionLab;
34
using System;
45
using System.Collections.Generic;
56
using System.ComponentModel;
@@ -143,6 +144,10 @@ public string DetectStatus
143144
set => SetField(ref detectStatus, value ?? string.Empty);
144145
}
145146

147+
public string LocalizedLabelStatus => LocalizeStatus(labelStatus, "WpfImageQueue.Row.SavePrefix");
148+
149+
public string LocalizedDetectStatus => LocalizeStatus(detectStatus, "WpfImageQueue.Row.InspectPrefix");
150+
146151
public string Dimensions
147152
{
148153
get => dimensions;
@@ -159,6 +164,10 @@ public string Detail
159164

160165
public string QueueRowAccessibleName => BuildQueueRowText(" / ");
161166

167+
public string LocalizedQueueRowToolTip => BuildLocalizedQueueRowText(Environment.NewLine);
168+
169+
public string LocalizedQueueRowAccessibleName => BuildLocalizedQueueRowText(" / ");
170+
162171
public string QueueStatusSummary
163172
{
164173
get => queueStatusSummary;
@@ -171,6 +180,10 @@ public string QueueBadgeText
171180
set => SetField(ref queueBadgeText, value ?? string.Empty);
172181
}
173182

183+
public string LocalizedQueueBadgeText => LocalizeBadgeText(queueBadgeText);
184+
185+
public string LocalizedQueueStatusSummary => LocalizeSummary(queueStatusSummary);
186+
174187
public PackIconMaterialKind QueueIconKind
175188
{
176189
get => queueIconKind;
@@ -225,6 +238,16 @@ public AnomalyImageReviewState AnomalyReviewState
225238
set => SetField(ref anomalyReviewState, value);
226239
}
227240

241+
internal void RefreshLocalizedPresentation()
242+
{
243+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedLabelStatus)));
244+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedDetectStatus)));
245+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueBadgeText)));
246+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueStatusSummary)));
247+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueRowToolTip)));
248+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueRowAccessibleName)));
249+
}
250+
228251
public static WpfImageQueueItem CreateShell(string imagePath)
229252
{
230253
return CreateShell(WpfImageQueueCatalogEntry.Create(imagePath));
@@ -255,11 +278,171 @@ private bool SetField<T>(ref T field, T value, [CallerMemberName] string propert
255278
{
256279
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(QueueRowToolTip)));
257280
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(QueueRowAccessibleName)));
281+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedLabelStatus)));
282+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedDetectStatus)));
283+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueStatusSummary)));
284+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueRowToolTip)));
285+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueRowAccessibleName)));
286+
}
287+
288+
if (string.Equals(propertyName, nameof(QueueBadgeText), StringComparison.Ordinal))
289+
{
290+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocalizedQueueBadgeText)));
258291
}
259292

260293
return true;
261294
}
262295

296+
private string BuildLocalizedQueueRowText(string separator)
297+
{
298+
string normalizedSeparator = string.IsNullOrEmpty(separator) ? " / " : separator;
299+
var parts = new List<string>();
300+
if (!string.IsNullOrWhiteSpace(FileName))
301+
{
302+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.FilePrefix"), FileName.Trim()));
303+
}
304+
305+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.SavePrefix"), LocalizedLabelStatus));
306+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.InspectPrefix"), LocalizedDetectStatus));
307+
if (!string.IsNullOrWhiteSpace(Dimensions))
308+
{
309+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.SizePrefix"), Dimensions.Trim()));
310+
}
311+
312+
if (!string.IsNullOrWhiteSpace(LocalizedQueueStatusSummary))
313+
{
314+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.StatusPrefix"), LocalizedQueueStatusSummary));
315+
}
316+
317+
if (!string.IsNullOrWhiteSpace(Detail))
318+
{
319+
parts.Add(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", T("WpfImageQueue.Row.DetailPrefix"), LocalizeSummary(Detail)));
320+
}
321+
322+
return string.Join(normalizedSeparator, parts);
323+
}
324+
325+
private static string LocalizeStatus(string value, string prefixKey)
326+
{
327+
string normalized = string.IsNullOrWhiteSpace(value) ? string.Empty : NormalizeInlineText(value);
328+
if (OpenVisionLanguageService.CurrentLanguage == OpenVisionLanguage.Korean)
329+
{
330+
if (!string.IsNullOrWhiteSpace(normalized))
331+
{
332+
return normalized;
333+
}
334+
335+
return string.Equals(prefixKey, "WpfImageQueue.Row.SavePrefix", StringComparison.Ordinal)
336+
? "없음"
337+
: "대기";
338+
}
339+
340+
if (string.IsNullOrWhiteSpace(normalized))
341+
{
342+
return string.Equals(prefixKey, "WpfImageQueue.Row.SavePrefix", StringComparison.Ordinal)
343+
? "None"
344+
: "Waiting";
345+
}
346+
347+
return TranslateKnownStatus(normalized);
348+
}
349+
350+
private static string LocalizeBadgeText(string value)
351+
{
352+
string normalized = string.IsNullOrWhiteSpace(value) ? string.Empty : NormalizeInlineText(value);
353+
if (OpenVisionLanguageService.CurrentLanguage == OpenVisionLanguage.Korean)
354+
{
355+
return normalized;
356+
}
357+
358+
return TranslateKnownStatus(normalized);
359+
}
360+
361+
private static string LocalizeSummary(string value)
362+
{
363+
string normalized = NormalizeInlineText(value);
364+
if (OpenVisionLanguageService.CurrentLanguage == OpenVisionLanguage.Korean
365+
|| string.IsNullOrWhiteSpace(normalized))
366+
{
367+
return normalized;
368+
}
369+
370+
string localized = normalized
371+
.Replace("상태 확인 중", "Checking status", StringComparison.Ordinal)
372+
.Replace("수정 필요: 라벨", "Needs fix: labels", StringComparison.Ordinal)
373+
.Replace("검수 완료: 라벨", "Reviewed: labels", StringComparison.Ordinal)
374+
.Replace("저장 완료: 라벨", "Saved: labels", StringComparison.Ordinal)
375+
.Replace("객체 없음 완료: 라벨", "No object: labels", StringComparison.Ordinal)
376+
.Replace("후보 숨김 완료: 라벨", "Candidate hidden: labels", StringComparison.Ordinal)
377+
.Replace("저장 라벨", "Saved labels", StringComparison.Ordinal)
378+
.Replace("AI 후보", "AI candidates", StringComparison.Ordinal)
379+
.Replace("검사 실패", "Inspection failed", StringComparison.Ordinal)
380+
.Replace("검사중", "Inspecting", StringComparison.Ordinal)
381+
.Replace("저장 필요", "Save required", StringComparison.Ordinal)
382+
.Replace("수정 필요", "Needs fix", StringComparison.Ordinal)
383+
.Replace("검수 완료", "Reviewed", StringComparison.Ordinal)
384+
.Replace("객체 없음", "No object", StringComparison.Ordinal)
385+
.Replace("후보 숨김", "Candidate hidden", StringComparison.Ordinal)
386+
.Replace("완료", "Complete", StringComparison.Ordinal)
387+
.Replace("대기", "Waiting", StringComparison.Ordinal)
388+
.Replace("없음", "None", StringComparison.Ordinal)
389+
.Replace("확인 필요", "Review needed", StringComparison.Ordinal);
390+
return localized;
391+
}
392+
393+
private static string TranslateKnownStatus(string value)
394+
{
395+
if (string.IsNullOrWhiteSpace(value))
396+
{
397+
return string.Empty;
398+
}
399+
400+
if (value.StartsWith("AI 후보 ", StringComparison.Ordinal))
401+
{
402+
return "AI candidates " + value.Substring("AI 후보 ".Length);
403+
}
404+
405+
if (value.StartsWith("실패 x", StringComparison.Ordinal))
406+
{
407+
return "Failed x" + value.Substring("실패 x".Length);
408+
}
409+
410+
if (value.StartsWith("실패 ", StringComparison.Ordinal))
411+
{
412+
return "Failed " + value.Substring("실패 ".Length);
413+
}
414+
415+
if (value.EndsWith("개", StringComparison.Ordinal)
416+
&& value.Length > 1
417+
&& value.Substring(0, value.Length - 1).All(char.IsDigit))
418+
{
419+
return value.Substring(0, value.Length - 1) + " items";
420+
}
421+
422+
return value switch
423+
{
424+
"확인중" => "Checking",
425+
"대기" => "Waiting",
426+
"없음" => "None",
427+
"객체 없음" => "No object",
428+
"객체없음" => "No object",
429+
"저장 필요" => "Save required",
430+
"검사중" => "Inspecting",
431+
"실패" => "Failed",
432+
"저장됨" => "Saved",
433+
"후보 숨김" => "Candidate hidden",
434+
"수정 필요" => "Needs fix",
435+
"검수 완료" => "Reviewed",
436+
"미판정" => "Unreviewed",
437+
"완료" => "Complete",
438+
"확인 필요" => "Review needed",
439+
"작업" => "Work",
440+
_ => value
441+
};
442+
}
443+
444+
private static string T(string key) => OpenVisionLanguageService.T(key);
445+
263446
private string BuildQueueRowText(string separator)
264447
{
265448
string normalizedSeparator = string.IsNullOrEmpty(separator) ? " / " : separator;
@@ -374,11 +557,17 @@ private static Brush CreateFrozenBrush(string color)
374557
}
375558
}
376559

377-
public sealed class WpfImageQueueFilterOption
560+
public sealed class WpfImageQueueFilterOption : INotifyPropertyChanged
378561
{
379562
public WpfImageQueueFilter Filter { get; set; }
380563

381-
public string Text { get; set; } = string.Empty;
564+
public string TextKey { get; set; } = string.Empty;
565+
566+
public string Text => string.IsNullOrWhiteSpace(TextKey)
567+
? string.Empty
568+
: OpenVisionLanguageService.T(TextKey);
569+
570+
public event PropertyChangedEventHandler PropertyChanged;
382571

383572
public static IReadOnlyList<WpfImageQueueFilterOption> CreateDefaults()
384573
{
@@ -387,26 +576,36 @@ public static IReadOnlyList<WpfImageQueueFilterOption> CreateDefaults()
387576
.Select(filter => new WpfImageQueueFilterOption
388577
{
389578
Filter = filter,
390-
Text = GetDisplayName(filter)
579+
TextKey = GetTextKey(filter)
391580
})
392581
.ToList();
393582
}
394583

395-
public static string GetDisplayName(WpfImageQueueFilter filter)
584+
internal void RefreshLocalizedPresentation()
585+
{
586+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
587+
}
588+
589+
private static string GetTextKey(WpfImageQueueFilter filter)
396590
{
397591
return filter switch
398592
{
399-
WpfImageQueueFilter.Unlabeled => "확인 필요",
400-
WpfImageQueueFilter.NeedsFix => "수정 필요",
401-
WpfImageQueueFilter.Requested => "검사중",
402-
WpfImageQueueFilter.Candidate => "AI 후보",
403-
WpfImageQueueFilter.Confirmed => "저장됨",
404-
WpfImageQueueFilter.Skipped => "숨김",
405-
WpfImageQueueFilter.NoCandidate => "객체없음",
406-
WpfImageQueueFilter.Failed => "실패",
407-
_ => "전체"
593+
WpfImageQueueFilter.Unlabeled => "WpfImageQueue.FilterOption.Unfinished",
594+
WpfImageQueueFilter.NeedsFix => "WpfImageQueue.FilterOption.NeedsFix",
595+
WpfImageQueueFilter.Requested => "WpfImageQueue.FilterOption.Requested",
596+
WpfImageQueueFilter.Candidate => "WpfImageQueue.FilterOption.Candidate",
597+
WpfImageQueueFilter.Confirmed => "WpfImageQueue.FilterOption.Confirmed",
598+
WpfImageQueueFilter.Skipped => "WpfImageQueue.FilterOption.Skipped",
599+
WpfImageQueueFilter.NoCandidate => "WpfImageQueue.FilterOption.NoCandidate",
600+
WpfImageQueueFilter.Failed => "WpfImageQueue.FilterOption.Failed",
601+
_ => "WpfImageQueue.FilterOption.All"
408602
};
409603
}
604+
605+
public static string GetDisplayName(WpfImageQueueFilter filter)
606+
{
607+
return OpenVisionLanguageService.T(GetTextKey(filter));
608+
}
410609
}
411610

412611
public enum WpfImageQueueFilter

0 commit comments

Comments
 (0)