From d8b7a4722227d7e07b92af9ba99b514d016e5e3c Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 13:01:18 +0900 Subject: [PATCH 1/4] =?UTF-8?q?chore:=20l10n=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/HeatmapWidget.swift | 2 +- .../HeatmapWidgetConfigurationIntent.swift | 2 +- .../Heatmap/HeatmapWidgetEntryView.swift | 10 +- DevLogWidget/Heatmap/WidgetHeatmapGrid.swift | 12 +- DevLogWidget/Resource/Localizable.xcstrings | 154 ++++++++++++++++++ DevLogWidget/Today/TodayTodoWidget.swift | 2 +- .../TodayTodoWidgetConfigurationIntent.swift | 2 +- .../Today/TodayTodoWidgetEntryView.swift | 4 +- 8 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 DevLogWidget/Resource/Localizable.xcstrings diff --git a/DevLogWidget/Heatmap/HeatmapWidget.swift b/DevLogWidget/Heatmap/HeatmapWidget.swift index 0561e4d4..9f30cce4 100644 --- a/DevLogWidget/Heatmap/HeatmapWidget.swift +++ b/DevLogWidget/Heatmap/HeatmapWidget.swift @@ -23,7 +23,7 @@ struct HeatmapWidget: Widget { .widgetURL(WidgetDeepLink.heatmapURL) } .configurationDisplayName("Heatmap") - .description("활동 히트맵을 표시합니다.") + .description("widget_heatmap_description") .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift index 159460e3..f14c7841 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift @@ -10,5 +10,5 @@ import WidgetKit struct HeatmapWidgetConfigurationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Heatmap" - static var description = IntentDescription("활동 히트맵을 표시합니다.") + static var description = IntentDescription("widget_heatmap_description") } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift b/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift index 10c550c5..4a437923 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift @@ -28,7 +28,7 @@ struct HeatmapWidgetEntryView: View { switch widgetFamily { case .systemSmall: VStack(alignment: .leading, spacing: 4) { - header(title: "이번 달 히트맵") + header(title: "widget_heatmap_current_month_title") WidgetHeatmapGrid( months: currentMonths(from: snapshot), selectedActivityKindRawValues: snapshot.selectedActivityKindRawValues, @@ -38,7 +38,7 @@ struct HeatmapWidgetEntryView: View { } case .systemMedium: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 분기 히트맵") + header(title: "widget_heatmap_current_quarter_title") WidgetHeatmapGrid( months: snapshot.months, selectedActivityKindRawValues: snapshot.selectedActivityKindRawValues, @@ -58,7 +58,7 @@ struct HeatmapWidgetEntryView: View { switch widgetFamily { case .systemSmall: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 달 히트맵") + header(title: "widget_heatmap_current_month_title") WidgetHeatmapPlaceholderGrid( months: shape.currentMonths, showsMonthTitles: false @@ -66,7 +66,7 @@ struct HeatmapWidgetEntryView: View { } case .systemMedium: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 분기 히트맵") + header(title: "widget_heatmap_current_quarter_title") WidgetHeatmapPlaceholderGrid( months: shape.quarterMonths, showsMonthTitles: true @@ -77,7 +77,7 @@ struct HeatmapWidgetEntryView: View { } } - private func header(title: String) -> some View { + private func header(title: LocalizedStringKey) -> some View { HStack(alignment: .firstTextBaseline, spacing: 6) { Text(title) .font(.headline) diff --git a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift index b21a49c4..0ab3937c 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift @@ -74,10 +74,10 @@ struct WidgetHeatmapPlaceholderGrid: View { private struct WidgetHeatmapWeekdayLabels: View { let layout: WidgetHeatmapLayout private let orderedWeekdays = Array(1...7) - private let weekdayLabels = [ - 2: "월", - 4: "수", - 6: "금" + private let weekdayLocalizedStringKeys: [Int: LocalizedStringKey] = [ + 2: "widget_heatmap_weekday_monday", + 4: "widget_heatmap_weekday_wednesday", + 6: "widget_heatmap_weekday_friday" ] var body: some View { @@ -91,8 +91,8 @@ private struct WidgetHeatmapWeekdayLabels: View { @ViewBuilder private func weekdayLabel(for weekday: Int) -> some View { - if let label = weekdayLabels[weekday] { - Text(label) + if let localizedStringKey = weekdayLocalizedStringKeys[weekday] { + Text(localizedStringKey) .font(.caption2) .foregroundStyle(.secondary) .frame( diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings new file mode 100644 index 00000000..39acec2c --- /dev/null +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -0,0 +1,154 @@ +{ + "sourceLanguage" : "ko", + "strings" : { + "#%lld" : { + + }, + "%lld" : { + + }, + "Heatmap" : { + + }, + "Today" : { + + }, + "widget_heatmap_current_month_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This Month Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이번 달 히트맵" + } + } + } + }, + "widget_heatmap_current_quarter_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This Quarter Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이번 분기 히트맵" + } + } + } + }, + "widget_heatmap_weekday_friday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fri" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "금" + } + } + } + }, + "widget_heatmap_weekday_monday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mon" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "월" + } + } + } + }, + "widget_heatmap_weekday_wednesday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wed" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "수" + } + } + } + }, + "widget_heatmap_description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shows your activity heatmap." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "활동 히트맵을 표시합니다." + } + } + } + }, + "widget_today_description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shows today's Todo list." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "오늘 기준 Todo 목록을 표시합니다." + } + } + } + }, + "widget_today_empty_message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No tasks for today.\nTake a short break!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!" + } + } + } + } + }, + "version" : "1.0" +} diff --git a/DevLogWidget/Today/TodayTodoWidget.swift b/DevLogWidget/Today/TodayTodoWidget.swift index 9e0a7c67..e002c9b5 100644 --- a/DevLogWidget/Today/TodayTodoWidget.swift +++ b/DevLogWidget/Today/TodayTodoWidget.swift @@ -22,7 +22,7 @@ struct TodayTodoWidget: Widget { .containerBackground(.fill.tertiary, for: .widget) .widgetURL(WidgetDeepLink.todayTodoURL) } - .description("오늘 기준 Todo 목록을 표시합니다.") + .description("widget_today_description") .configurationDisplayName("Today") .supportedFamilies([.systemSmall, .systemMedium]) } diff --git a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift index 649d085e..111cf4a4 100644 --- a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift +++ b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift @@ -10,5 +10,5 @@ import WidgetKit struct TodayTodoWidgetConfigurationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Today" - static var description = IntentDescription("오늘 기준 Todo 목록을 표시합니다.") + static var description = IntentDescription("widget_today_description") } diff --git a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift index 9e4a949b..4b9f7177 100644 --- a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift +++ b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift @@ -41,7 +41,7 @@ struct TodayTodoWidgetEntryView: View { if let item = displayedItems(from: snapshot).first { todoRow(item) } else { - Text("오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!") + Text("widget_today_empty_message") .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) @@ -51,7 +51,7 @@ struct TodayTodoWidgetEntryView: View { let items = displayedItems(from: snapshot) VStack(alignment: .leading, spacing: 6) { if items.isEmpty { - Text("오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!") + Text("widget_today_empty_message") .multilineTextAlignment(.center) .font(.caption) .foregroundStyle(.secondary) From 04a65cb155b0894af8320cd99ce44f64cdb5914c Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 13:29:11 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20Xcode=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=ED=99=94=20=EC=84=A4=EC=A0=95=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog.xcodeproj/project.pbxproj | 5 ++++- .../xcshareddata/xcschemes/DevLog.xcscheme | 2 +- .../xcschemes/DevLogWidgetExtension.xcscheme | 16 +++++++++++++--- .../xcshareddata/xcschemes/DevLog_Unit.xcscheme | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/DevLog.xcodeproj/project.pbxproj b/DevLog.xcodeproj/project.pbxproj index dcb2743b..74e9bf8b 100644 --- a/DevLog.xcodeproj/project.pbxproj +++ b/DevLog.xcodeproj/project.pbxproj @@ -275,7 +275,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 2640; - LastUpgradeCheck = 2600; + LastUpgradeCheck = 2640; TargetAttributes = { DF3416442E45F67C00F9312B = { CreatedOnToolsVersion = 16.3; @@ -295,6 +295,7 @@ knownRegions = ( ko, en, + Base, ); mainGroup = DFD48AF72DC4D6E2005905C5; minimizedProjectReferenceProxies = 1; @@ -603,6 +604,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -671,6 +673,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; diff --git a/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme b/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme index 5b1f7f8b..2a843515 100644 --- a/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme +++ b/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme @@ -1,6 +1,6 @@ - + + + + + - + Date: Fri, 1 May 2026 15:35:26 +0900 Subject: [PATCH 3/4] =?UTF-8?q?ui:=20'Heatmap'=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Resource/Localizable.xcstrings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 39acec2c..556c302e 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -19,7 +19,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This Month Heatmap" + "value" : "This Month" } }, "ko" : { @@ -36,7 +36,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This Quarter Heatmap" + "value" : "This Quarter" } }, "ko" : { From c17c9975efe701d3ba4533721fd2933b1736d505 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 15:40:38 +0900 Subject: [PATCH 4/4] =?UTF-8?q?ui:=20=EC=9A=94=EC=9D=BC=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/WidgetHeatmapGrid.swift | 61 +++---------------- .../Heatmap/WidgetHeatmapLayout.swift | 14 +---- DevLogWidget/Resource/Localizable.xcstrings | 51 ---------------- 3 files changed, 11 insertions(+), 115 deletions(-) diff --git a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift index 0ab3937c..a7519d88 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift @@ -22,19 +22,15 @@ struct WidgetHeatmapGrid: View { showsMonthTitles: showsMonthTitles ) - HStack(alignment: .top, spacing: layout.weekdayLabelSpacing) { - WidgetHeatmapWeekdayLabels(layout: layout) - - HStack(alignment: .top, spacing: layout.monthSpacing) { - ForEach(months, id: \.monthStart) { month in - WidgetHeatmapMonthGrid( - month: month, - layout: layout, - selectedActivityKindRawValues: selectedActivityKindRawValues, - maxCount: maxCount, - showsMonthTitle: showsMonthTitles - ) - } + HStack(alignment: .top, spacing: layout.monthSpacing) { + ForEach(months, id: \.monthStart) { month in + WidgetHeatmapMonthGrid( + month: month, + layout: layout, + selectedActivityKindRawValues: selectedActivityKindRawValues, + maxCount: maxCount, + showsMonthTitle: showsMonthTitles + ) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) @@ -71,45 +67,6 @@ struct WidgetHeatmapPlaceholderGrid: View { } } -private struct WidgetHeatmapWeekdayLabels: View { - let layout: WidgetHeatmapLayout - private let orderedWeekdays = Array(1...7) - private let weekdayLocalizedStringKeys: [Int: LocalizedStringKey] = [ - 2: "widget_heatmap_weekday_monday", - 4: "widget_heatmap_weekday_wednesday", - 6: "widget_heatmap_weekday_friday" - ] - - var body: some View { - VStack(alignment: .leading, spacing: layout.cellSpacing) { - ForEach(orderedWeekdays, id: \.self) { weekday in - weekdayLabel(for: weekday) - } - } - .padding(.top, layout.weekdayTopPadding) - } - - @ViewBuilder - private func weekdayLabel(for weekday: Int) -> some View { - if let localizedStringKey = weekdayLocalizedStringKeys[weekday] { - Text(localizedStringKey) - .font(.caption2) - .foregroundStyle(.secondary) - .frame( - width: layout.weekdayLabelWidth, - height: layout.cellSize, - alignment: .leading - ) - } else { - Color.clear - .frame( - width: layout.weekdayLabelWidth, - height: layout.cellSize - ) - } - } -} - private struct WidgetHeatmapMonthGrid: View { let month: WidgetHeatmapMonthSnapshot let layout: WidgetHeatmapLayout diff --git a/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift b/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift index f5eff1e8..10b76a18 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift @@ -12,8 +12,6 @@ struct WidgetHeatmapLayout { let cellSpacing: CGFloat let monthSpacing: CGFloat let monthTitleSpacing: CGFloat - let weekdayLabelSpacing: CGFloat = Self.baseWeekdayLabelSpacing - let weekdayLabelWidth: CGFloat = Self.baseWeekdayLabelWidth let showsMonthTitles: Bool init( @@ -37,10 +35,6 @@ struct WidgetHeatmapLayout { ) } - var weekdayTopPadding: CGFloat { - showsMonthTitles ? cellSize + monthTitleSpacing : 0 - } - var cellCornerRadius: CGFloat { max(2, cellSize * 0.2) } @@ -49,8 +43,6 @@ struct WidgetHeatmapLayout { private static let baseMonthSpacing: CGFloat = 10 private static let maxMonthSpacing: CGFloat = 26 private static let baseMonthTitleSpacing: CGFloat = 4 - private static let baseWeekdayLabelSpacing: CGFloat = 5 - private static let baseWeekdayLabelWidth: CGFloat = 14 private static func resolvedMonthTitleSpacing(showsMonthTitles: Bool) -> CGFloat { // 월 제목을 표시하는 Medium에서만 제목과 셀 사이 간격을 확보한다. @@ -99,10 +91,8 @@ struct WidgetHeatmapLayout { ) -> CGFloat { // 셀 크기는 높이 기준으로 고정하고, 남는 가로폭만 월 간격 계산에 사용한다. let sanitizedWeekCounts = sanitizedWeekCounts(weekCounts) - // 요일 라벨 영역, 전체 셀 컬럼, 월 내부 주차 spacing을 더해 기본 너비를 구한다. - let contentWidth = baseWeekdayLabelWidth - + baseWeekdayLabelSpacing - + cellSize * CGFloat(totalColumns(in: sanitizedWeekCounts)) + // 전체 셀 컬럼과 월 내부 주차 spacing을 더해 기본 너비를 구한다. + let contentWidth = cellSize * CGFloat(totalColumns(in: sanitizedWeekCounts)) + baseCellSpacing * CGFloat(totalColumnSpacings(in: sanitizedWeekCounts)) // 기본 너비보다 위젯이 넓을 때만 월 간격에 분배할 여유 폭이 생긴다. return max(0, availableWidth - contentWidth) diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 556c302e..3a61a290 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -47,57 +47,6 @@ } } }, - "widget_heatmap_weekday_friday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fri" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "금" - } - } - } - }, - "widget_heatmap_weekday_monday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mon" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "월" - } - } - } - }, - "widget_heatmap_weekday_wednesday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wed" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "수" - } - } - } - }, "widget_heatmap_description" : { "extractionState" : "manual", "localizations" : {