✨ iOS check in widget with notable days support
This commit is contained in:
@@ -631,6 +631,10 @@
|
||||
en,
|
||||
Base,
|
||||
"zh-Hans",
|
||||
es,
|
||||
ja,
|
||||
ko,
|
||||
"zh-Hant",
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
preferredProjectObjectVersion = 77;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -14,3 +14,4 @@
|
||||
"loading" = "Loading...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d XP";
|
||||
"footer" = "Solian Journal";
|
||||
|
||||
@@ -60,6 +60,34 @@ struct CheckInResult: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
struct NotableDay: Codable {
|
||||
let date: String
|
||||
let localName: String
|
||||
let globalName: String
|
||||
let countryCode: String?
|
||||
let localizableKey: String?
|
||||
let holidays: [Int]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case date
|
||||
case localName = "local_name"
|
||||
case globalName = "global_name"
|
||||
case countryCode = "country_code"
|
||||
case localizableKey = "localizable_key"
|
||||
case holidays
|
||||
}
|
||||
|
||||
var notableDate: Date? {
|
||||
ISO8601DateFormatter().date(from: date)
|
||||
}
|
||||
|
||||
var isToday: Bool {
|
||||
guard let notableDate = notableDate else { return false }
|
||||
let calendar = Calendar.current
|
||||
return calendar.isDateInToday(notableDate)
|
||||
}
|
||||
}
|
||||
|
||||
enum RemoteError: Error {
|
||||
case missingCredentials
|
||||
case invalidURL
|
||||
@@ -152,19 +180,37 @@ class WidgetNetworkService {
|
||||
|
||||
request.timeoutInterval = 10.0
|
||||
|
||||
print("[WidgetKit] [Network] Requesting: \(baseURL)\(path)")
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw RemoteError.invalidResponse
|
||||
}
|
||||
|
||||
print("[WidgetKit] [Network] Status: \(httpResponse.statusCode), Data length: \(data.count)")
|
||||
|
||||
if let jsonString = String(data: data, encoding: .utf8) {
|
||||
print("[WidgetKit] [Network] Response: \(jsonString.prefix(500))")
|
||||
}
|
||||
|
||||
switch httpResponse.statusCode {
|
||||
case 200...299:
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
do {
|
||||
let result = try decoder.decode(T.self, from: data)
|
||||
print("[WidgetKit] [Network] Successfully decoded response")
|
||||
return result
|
||||
} catch {
|
||||
print("[WidgetKit] [Network] Decoding error: \(error.localizedDescription)")
|
||||
print("[WidgetKit] [Network] Expected type: \(String(describing: T.self))")
|
||||
throw RemoteError.decodingError
|
||||
}
|
||||
case 404:
|
||||
print("[WidgetKit] [Network] Resource not found (404)")
|
||||
return nil
|
||||
default:
|
||||
print("[WidgetKit] [Network] HTTP Error: \(httpResponse.statusCode)")
|
||||
throw RemoteError.httpError(httpResponse.statusCode)
|
||||
}
|
||||
}
|
||||
@@ -178,14 +224,73 @@ class CheckInService {
|
||||
}
|
||||
}
|
||||
|
||||
class NotableDayService {
|
||||
private let networkService = WidgetNetworkService()
|
||||
|
||||
func fetchRecentNotableDay() async throws -> NotableDay? {
|
||||
print("[WidgetKit] [NotableDayService] Fetching recent notable day...")
|
||||
do {
|
||||
let result: [NotableDay]? = try await networkService.makeRequest(path: "/pass/notable/me/recent")
|
||||
print("[WidgetKit] [NotableDayService] Result: \(String(describing: result))")
|
||||
|
||||
guard let result = result else {
|
||||
print("[WidgetKit] [NotableDayService] Result is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
print("[WidgetKit] [NotableDayService] Result count: \(result.count)")
|
||||
|
||||
guard result.isEmpty == false else {
|
||||
print("[WidgetKit] [NotableDayService] No notable days found")
|
||||
return nil
|
||||
}
|
||||
|
||||
let firstDay = result.first!
|
||||
print("[WidgetKit] [NotableDayService] First notable day: \(firstDay.localName), date: \(firstDay.date)")
|
||||
|
||||
return firstDay
|
||||
} catch let decodingError as DecodingError {
|
||||
print("[WidgetKit] [NotableDayService] Decoding error, trying as single object...")
|
||||
print("[WidgetKit] [NotableDayService] Error: \(decodingError.localizedDescription)")
|
||||
|
||||
switch decodingError {
|
||||
case .typeMismatch(let type, let context):
|
||||
print("[WidgetKit] [NotableDayService] Type mismatch: expected \(type), context: \(context.debugDescription)")
|
||||
case .valueNotFound(let type, let context):
|
||||
print("[WidgetKit] [NotableDayService] Value not found: type \(type), context: \(context.debugDescription)")
|
||||
case .keyNotFound(let key, let context):
|
||||
print("[WidgetKit] [NotableDayService] Key not found: \(key), context: \(context.debugDescription)")
|
||||
case .dataCorrupted(let context):
|
||||
print("[WidgetKit] [NotableDayService] Data corrupted: \(context.debugDescription)")
|
||||
@unknown default:
|
||||
print("[WidgetKit] [NotableDayService] Unknown decoding error")
|
||||
}
|
||||
|
||||
do {
|
||||
let singleResult: NotableDay? = try await networkService.makeRequest(path: "/pass/notable/me/recent")
|
||||
print("[WidgetKit] [NotableDayService] Single object decode succeeded: \(singleResult?.localName ?? "nil")")
|
||||
return singleResult
|
||||
} catch {
|
||||
print("[WidgetKit] [NotableDayService] Single object decode also failed: \(error.localizedDescription)")
|
||||
throw decodingError
|
||||
}
|
||||
} catch {
|
||||
print("[WidgetKit] [NotableDayService] Error fetching notable day: \(error.localizedDescription)")
|
||||
print("[WidgetKit] [NotableDayService] Error type: \(type(of: error))")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckInEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let result: CheckInResult?
|
||||
let notableDay: NotableDay?
|
||||
let error: String?
|
||||
let isLoading: Bool
|
||||
|
||||
static func placeholder() -> CheckInEntry {
|
||||
CheckInEntry(date: Date(), result: nil, error: nil, isLoading: true)
|
||||
CheckInEntry(date: Date(), result: nil, notableDay: nil, error: nil, isLoading: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,22 +300,38 @@ struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> CheckInEntry {
|
||||
CheckInEntry.placeholder()
|
||||
}
|
||||
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
||||
Task {
|
||||
let result = try? await apiService.fetchCheckInResult()
|
||||
let entry = CheckInEntry(date: Date(), result: result, error: nil, isLoading: false)
|
||||
print("[WidgetKit] [Provider] Getting snapshot...")
|
||||
async let checkInResult = try? await apiService.fetchCheckInResult()
|
||||
async let notableDay = try? await NotableDayService().fetchRecentNotableDay()
|
||||
|
||||
let result = try? await checkInResult
|
||||
let day = try? await notableDay
|
||||
|
||||
print("[WidgetKit] [Provider] Snapshot - CheckIn: \(result != nil ? "Found" : "Not found"), NotableDay: \(day != nil ? "Found" : "Not found")")
|
||||
|
||||
let entry = CheckInEntry(date: Date(), result: result, notableDay: day, error: nil, isLoading: false)
|
||||
completion(entry)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
Task {
|
||||
let currentDate = Date()
|
||||
print("[WidgetKit] [Provider] Getting timeline at \(currentDate)...")
|
||||
|
||||
do {
|
||||
let result = try await apiService.fetchCheckInResult()
|
||||
let entry = CheckInEntry(date: currentDate, result: result, error: nil, isLoading: false)
|
||||
async let checkInResult = try await apiService.fetchCheckInResult()
|
||||
async let notableDay = try await NotableDayService().fetchRecentNotableDay()
|
||||
|
||||
let result = try await checkInResult
|
||||
let day = try await notableDay
|
||||
|
||||
print("[WidgetKit] [Provider] Timeline - CheckIn: \(result != nil ? "Found" : "Not found"), NotableDay: \(day != nil ? "Found" : "Not found")")
|
||||
|
||||
let entry = CheckInEntry(date: currentDate, result: result, notableDay: day, error: nil, isLoading: false)
|
||||
|
||||
let nextUpdateDate: Date
|
||||
if let result = result, let createdDate = result.createdDate {
|
||||
@@ -224,10 +345,12 @@ struct Provider: TimelineProvider {
|
||||
nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
|
||||
}
|
||||
|
||||
print("[WidgetKit] [Provider] Next update at: \(nextUpdateDate)")
|
||||
let timeline = Timeline(entries: [entry], policy: .after(nextUpdateDate))
|
||||
completion(timeline)
|
||||
} catch {
|
||||
let entry = CheckInEntry(date: currentDate, result: nil, error: error.localizedDescription, isLoading: false)
|
||||
print("[WidgetKit] [Provider] Error in getTimeline: \(error.localizedDescription)")
|
||||
let entry = CheckInEntry(date: currentDate, result: nil, notableDay: nil, error: error.localizedDescription, isLoading: false)
|
||||
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 10, to: currentDate)!
|
||||
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
|
||||
completion(timeline)
|
||||
@@ -239,16 +362,16 @@ struct Provider: TimelineProvider {
|
||||
struct CheckInWidgetEntryView: View {
|
||||
var entry: Provider.Entry
|
||||
@Environment(\.widgetFamily) var family
|
||||
|
||||
|
||||
var body: some View {
|
||||
if let result = entry.result {
|
||||
CheckedInView(result: result)
|
||||
CheckedInView(result: result, notableDay: entry.notableDay)
|
||||
} else if entry.isLoading {
|
||||
LoadingView()
|
||||
} else if let error = entry.error {
|
||||
ErrorView(error: error)
|
||||
} else {
|
||||
NotCheckedInView()
|
||||
NotCheckedInView(notableDay: entry.notableDay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,12 +381,82 @@ struct CheckInWidgetEntryView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func CheckedInView(result: CheckInResult) -> some View {
|
||||
private func NotableDayView(notableDay: NotableDay) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if !notableDay.isToday {
|
||||
Text(NSLocalizedString("notableDayUpcoming", comment: "Upcoming"))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack(spacing: isAccessory ? 8 : 6) {
|
||||
Image(systemName: "sparkles")
|
||||
.foregroundColor(.orange)
|
||||
.font(isAccessory ? .caption : .subheadline)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
if notableDay.isToday {
|
||||
Text(String(format: NSLocalizedString("notableDayToday", comment: "{name} is today!"), notableDay.localName))
|
||||
.font(isAccessory ? .caption : .footnote)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(2)
|
||||
} else {
|
||||
if let notableDate = notableDay.notableDate {
|
||||
let dateString = isCompact ? formatDateCompact(notableDate) : formatDateRegular(notableDate)
|
||||
Text(String(format: NSLocalizedString("notableDayIs", comment: "{date} is {name}"), dateString, notableDay.localName))
|
||||
.font(isAccessory ? .caption : .footnote)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(2)
|
||||
} else {
|
||||
Text(notableDay.localName)
|
||||
.font(isAccessory ? .caption : .footnote)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if notableDay.isToday && !isAccessory {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "star.fill")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.orange)
|
||||
Text(NSLocalizedString("today", comment: "Today"))
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var isCompact: Bool {
|
||||
family == .systemSmall || isAccessory
|
||||
}
|
||||
|
||||
private func formatDateCompact(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "M/d"
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
private func formatDateRegular(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MMM d, yyyy"
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func CheckedInView(result: CheckInResult, notableDay: NotableDay?) -> some View {
|
||||
Link(destination: URL(string: "solian://dashboard")!) {
|
||||
VStack(alignment: .leading, spacing: isAccessory ? 2 : 8) {
|
||||
HStack(spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "flame.fill")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.orange)
|
||||
.font(isAccessory ? .caption : .title3)
|
||||
Text(getLevelName(for: result.level))
|
||||
.font(isAccessory ? .caption2 : .headline)
|
||||
@@ -276,9 +469,9 @@ struct CheckInWidgetEntryView: View {
|
||||
let positiveTips = result.tips.filter { $0.isPositive }
|
||||
let negativeTips = result.tips.filter { !$0.isPositive }
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
HStack(spacing: 2) {
|
||||
if let positiveTip = positiveTips.first {
|
||||
HStack(spacing: 2) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "hand.thumbsup.fill")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -289,7 +482,7 @@ struct CheckInWidgetEntryView: View {
|
||||
}
|
||||
}
|
||||
if let negativeTip = negativeTips.first {
|
||||
HStack(spacing: 2) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "hand.thumbsdown.fill")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -299,6 +492,7 @@ struct CheckInWidgetEntryView: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
} else if family == .systemSmall {
|
||||
let positiveTips = result.tips.filter { $0.isPositive }
|
||||
@@ -309,7 +503,7 @@ struct CheckInWidgetEntryView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "hand.thumbsup.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.green.opacity(0.8))
|
||||
Text(positiveTip.title)
|
||||
.font(.caption)
|
||||
.foregroundColor(.primary)
|
||||
@@ -320,7 +514,7 @@ struct CheckInWidgetEntryView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "hand.thumbsdown.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.red.opacity(0.8))
|
||||
Text(negativeTip.title)
|
||||
.font(.caption)
|
||||
.foregroundColor(.primary)
|
||||
@@ -337,7 +531,7 @@ struct CheckInWidgetEntryView: View {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "hand.thumbsup.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.green.opacity(0.8))
|
||||
ForEach(Array(positiveTips.prefix(3)), id: \.title) { tip in
|
||||
Text(tip.title)
|
||||
.font(.caption)
|
||||
@@ -356,7 +550,7 @@ struct CheckInWidgetEntryView: View {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "hand.thumbsdown.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.red.opacity(0.8))
|
||||
ForEach(Array(negativeTips.prefix(3)), id: \.title) { tip in
|
||||
Text(tip.title)
|
||||
.font(.caption)
|
||||
@@ -378,9 +572,14 @@ struct CheckInWidgetEntryView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
if !isAccessory {
|
||||
if let notableDay = notableDay {
|
||||
NotableDayView(notableDay: notableDay)
|
||||
}
|
||||
|
||||
if family == .systemLarge {
|
||||
Spacer()
|
||||
WidgetFooter()
|
||||
|
||||
}
|
||||
}
|
||||
.padding(isAccessory ? 0 : (family == .systemSmall ? 6 : 12))
|
||||
@@ -408,7 +607,7 @@ struct CheckInWidgetEntryView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func NotCheckedInView() -> some View {
|
||||
private func NotCheckedInView(notableDay: NotableDay?) -> some View {
|
||||
Link(destination: URL(string: "solian://dashboard")!) {
|
||||
VStack(alignment: .leading, spacing: isAccessory ? 2 : 8) {
|
||||
HStack(spacing: 4) {
|
||||
@@ -426,9 +625,15 @@ struct CheckInWidgetEntryView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
if let notableDay = notableDay {
|
||||
NotableDayView(notableDay: notableDay)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
WidgetFooter()
|
||||
} else if let notableDay = notableDay {
|
||||
NotableDayView(notableDay: notableDay)
|
||||
}
|
||||
}
|
||||
.padding(isAccessory ? 0 : (family == .systemSmall ? 6 : 12))
|
||||
@@ -491,12 +696,13 @@ struct CheckInWidgetEntryView: View {
|
||||
|
||||
struct SolianWidgetExtension: Widget {
|
||||
let kind: String = "SolianWidgetExtension"
|
||||
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
CheckInWidgetEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
.padding(.vertical, 8)
|
||||
} else {
|
||||
CheckInWidgetEntryView(entry: entry)
|
||||
.padding()
|
||||
@@ -509,18 +715,18 @@ struct SolianWidgetExtension: Widget {
|
||||
}
|
||||
|
||||
private var supportedFamilies: [WidgetFamily] {
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
return [.systemSmall, .systemMedium, .systemLarge, .accessoryRectangular]
|
||||
#else
|
||||
#else
|
||||
return [.systemSmall, .systemMedium, .systemLarge]
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
SolianWidgetExtension()
|
||||
} timeline: {
|
||||
CheckInEntry(date: .now, result: nil, error: nil, isLoading: false)
|
||||
CheckInEntry(date: .now, result: nil, notableDay: nil, error: nil, isLoading: false)
|
||||
}
|
||||
|
||||
#Preview(as: .systemMedium) {
|
||||
@@ -544,6 +750,14 @@ struct SolianWidgetExtension: Widget {
|
||||
updatedAt: ISO8601DateFormatter().string(from: Date()),
|
||||
deletedAt: nil
|
||||
),
|
||||
notableDay: NotableDay(
|
||||
date: ISO8601DateFormatter().string(from: Calendar.current.date(byAdding: .day, value: 5, to: Date())!),
|
||||
localName: "Christmas",
|
||||
globalName: "Christmas",
|
||||
countryCode: nil,
|
||||
localizableKey: nil,
|
||||
holidays: []
|
||||
),
|
||||
error: nil,
|
||||
isLoading: false
|
||||
)
|
||||
@@ -570,6 +784,14 @@ struct SolianWidgetExtension: Widget {
|
||||
updatedAt: ISO8601DateFormatter().string(from: Date()),
|
||||
deletedAt: nil
|
||||
),
|
||||
notableDay: NotableDay(
|
||||
date: ISO8601DateFormatter().string(from: Date()),
|
||||
localName: "New Year",
|
||||
globalName: "New Year",
|
||||
countryCode: nil,
|
||||
localizableKey: nil,
|
||||
holidays: []
|
||||
),
|
||||
error: nil,
|
||||
isLoading: false
|
||||
)
|
||||
|
||||
23
ios/SolianWidgetExtension/en.lproj/Localizable.strings
Normal file
23
ios/SolianWidgetExtension/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,23 @@
|
||||
/* Check In Level Names */
|
||||
"checkInResultT0" = "Great Misfortune";
|
||||
"checkInResultT1" = "Misfortune";
|
||||
"checkInResultT2" = "Moderate";
|
||||
"checkInResultT3" = "Fortune";
|
||||
"checkInResultT4" = "Great Fortune";
|
||||
"checkInResultT5" = "Special";
|
||||
|
||||
/* Widget UI Strings */
|
||||
"checkIn" = "Check In";
|
||||
"tapToCheckIn" = "Tap to check in today";
|
||||
"error" = "Error";
|
||||
"openAppToRefresh" = "Open app to refresh";
|
||||
"loading" = "Loading...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d EXP";
|
||||
"footer" = "Solian Check In";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@ is today!";
|
||||
"notableDayIs" = "%@ is %@";
|
||||
"notableDayUpcoming" = "Upcoming";
|
||||
"today" = "Today";
|
||||
22
ios/SolianWidgetExtension/es.lproj/Localizable.strings
Normal file
22
ios/SolianWidgetExtension/es.lproj/Localizable.strings
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Check In Level Names */
|
||||
"checkInResultT0" = "Gran Desventura";
|
||||
"checkInResultT1" = "Desventura";
|
||||
"checkInResultT2" = "Moderado";
|
||||
"checkInResultT3" = "Buena Fortuna";
|
||||
"checkInResultT4" = "Gran Fortuna";
|
||||
"checkInResultT5" = "Especial";
|
||||
|
||||
/* Widget UI Strings */
|
||||
"checkIn" = "Registrar";
|
||||
"tapToCheckIn" = "Toca para registrar hoy";
|
||||
"error" = "Error";
|
||||
"openAppToRefresh" = "Abre la aplicación para actualizar";
|
||||
"loading" = "Cargando...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d EXP";
|
||||
"footer" = "Registro Solian";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@ es hoy!";
|
||||
"notableDayIs" = "%@ es %@";
|
||||
"today" = "Hoy";
|
||||
22
ios/SolianWidgetExtension/ja.lproj/Localizable.strings
Normal file
22
ios/SolianWidgetExtension/ja.lproj/Localizable.strings
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Check In Level Names */
|
||||
"checkInResultT0" = "大凶";
|
||||
"checkInResultT1" = "凶";
|
||||
"checkInResultT2" = "中平";
|
||||
"checkInResultT3" = "吉";
|
||||
"checkInResultT4" = "大吉";
|
||||
"checkInResultT5" = "特殊";
|
||||
|
||||
/* Widget UI Strings */
|
||||
"checkIn" = "チェックイン";
|
||||
"tapToCheckIn" = "タップして今日チェックイン";
|
||||
"error" = "エラー";
|
||||
"openAppToRefresh" = "アプリを開いて更新";
|
||||
"loading" = "読み込み中...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d 経験値";
|
||||
"footer" = "Solian チェックイン";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@は今日です!";
|
||||
"notableDayIs" = "%@は%@です";
|
||||
"today" = "今日";
|
||||
22
ios/SolianWidgetExtension/ko.lproj/Localizable.strings
Normal file
22
ios/SolianWidgetExtension/ko.lproj/Localizable.strings
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Check In Level Names */
|
||||
"checkInResultT0" = "대흉";
|
||||
"checkInResultT1" = "흉";
|
||||
"checkInResultT2" = "중평";
|
||||
"checkInResultT3" = "길";
|
||||
"checkInResultT4" = "대길";
|
||||
"checkInResultT5" = "특수";
|
||||
|
||||
/* Widget UI Strings */
|
||||
"checkIn" = "체크인";
|
||||
"tapToCheckIn" = "탭하여 오늘 체크인";
|
||||
"error" = "오류";
|
||||
"openAppToRefresh" = "앱을 열어 새로고침";
|
||||
"loading" = "로딩 중...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d 경험치";
|
||||
"footer" = "Solian 체크인";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@ 오늘입니다!";
|
||||
"notableDayIs" = "%@ 은/는 %@";
|
||||
"today" = "오늘";
|
||||
@@ -14,3 +14,10 @@
|
||||
"loading" = "加载中...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d 经验值";
|
||||
"footer" = "Solian 签到";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@是今天!";
|
||||
"notableDayIs" = "%@ 是 %@";
|
||||
"notableDayUpcoming" = "接下来";
|
||||
"today" = "今天";
|
||||
|
||||
22
ios/SolianWidgetExtension/zh-Hant.lproj/Localizable.strings
Normal file
22
ios/SolianWidgetExtension/zh-Hant.lproj/Localizable.strings
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Check In Level Names */
|
||||
"checkInResultT0" = "大凶";
|
||||
"checkInResultT1" = "凶";
|
||||
"checkInResultT2" = "中平";
|
||||
"checkInResultT3" = "吉";
|
||||
"checkInResultT4" = "大吉";
|
||||
"checkInResultT5" = "特殊";
|
||||
|
||||
/* Widget UI Strings */
|
||||
"checkIn" = "打卡";
|
||||
"tapToCheckIn" = "點擊今日打卡";
|
||||
"error" = "錯誤";
|
||||
"openAppToRefresh" = "打開應用以刷新";
|
||||
"loading" = "載入中...";
|
||||
"rewardPoints" = "%d";
|
||||
"rewardExperience" = "%d 經驗值";
|
||||
"footer" = "Solian 簽到";
|
||||
|
||||
/* Notable Day Strings */
|
||||
"notableDayToday" = "%@是今天!";
|
||||
"notableDayIs" = "%@ 是 %@";
|
||||
"today" = "今天";
|
||||
Reference in New Issue
Block a user