time localisation

pull/1023/head
Ryan ZHAO 11 months ago
parent 392790a56f
commit acb528f190

@ -1,129 +1 @@
{
"sourceLanguage" : "en",
"strings" : {
"CFBundleDisplayName" : {
"comment" : "Bundle display name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Session"
}
}
}
},
"CFBundleGetInfoString" : {
"comment" : "Get Info string",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
}
},
"CFBundleName" : {
"comment" : "Bundle name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Session"
}
}
}
},
"New Message" : {
},
"NSAppleMusicUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session needs to use Apple Music to play media attachments."
}
},
"zh_CN" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session需要Apple Music的访问权限用以播放媒体附件。"
}
}
}
},
"NSCameraUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session needs camera access to take pictures and scan QR codes."
}
}
}
},
"NSFaceIDUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session's Screen Lock feature uses Face ID."
}
}
}
},
"NSHumanReadableCopyright" : {
"comment" : "Copyright (human-readable)",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "com.loki-project.loki-messenger"
}
}
}
},
"NSMicrophoneUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session needs access to your microphone for calls and to send to audio messages."
}
}
}
},
"NSPhotoLibraryAddUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session needs access to your library to save photos."
}
}
}
},
"NSPhotoLibraryUsageDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Session needs access to your library to update your avatar and send photos."
}
}
}
}
},
"version" : "1.0"
}
{"sourceLanguage":"en","version":"1.0","strings":{"NSAppleMusicUsageDescription":{"localizations":{"zh_CN":{"stringUnit":{"state":"translated","value":"Session需要Apple Music的访问权限用以播放媒体附件。"}},"en":{"stringUnit":{"state":"translated","value":"Session needs to use Apple Music to play media attachments."}}},"extractionState":"manual"},"NSHumanReadableCopyright":{"comment":"Copyright (human-readable)","localizations":{"en":{"stringUnit":{"state":"new","value":"com.loki-project.loki-messenger"}}},"extractionState":"extracted_with_value"},"New Message":{},"NSPhotoLibraryUsageDescription":{"localizations":{"en":{"stringUnit":{"state":"translated","value":"Session needs access to your library to update your avatar and send photos."}}},"extractionState":"manual"},"NSFaceIDUsageDescription":{"localizations":{"en":{"stringUnit":{"state":"translated","value":"Session's Screen Lock feature uses Face ID."}}},"extractionState":"manual"},"CFBundleName":{"comment":"Bundle name","localizations":{"en":{"stringUnit":{"state":"new","value":"Session"}}},"extractionState":"extracted_with_value"},"CFBundleDisplayName":{"comment":"Bundle display name","localizations":{"en":{"stringUnit":{"state":"new","value":"Session"}}},"extractionState":"extracted_with_value"},"NSPhotoLibraryAddUsageDescription":{"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Session needs access to your library to save photos."}}}},"NSCameraUsageDescription":{"localizations":{"en":{"stringUnit":{"state":"translated","value":"Session needs camera access to take pictures and scan QR codes."}}},"extractionState":"manual"},"CFBundleGetInfoString":{"comment":"Get Info string","localizations":{"en":{"stringUnit":{"state":"new","value":""}}},"extractionState":"extracted_with_value"},"NSMicrophoneUsageDescription":{"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Session needs access to your microphone for calls and to send to audio messages."}}}}}}

@ -715,20 +715,6 @@
"TXT_BLOCK_USER_TITLE" = "Block User";
"DM_ERROR_DIRECT_BLINDED_ID" = "You can only send messages to Blinded IDs from within a Community";
"LOADING_CONVERSATIONS" = "Loading Conversations...";
"TIME_AMOUNT_SECONDS" = "%@ seconds";
"TIME_AMOUNT_SINGLE_MINUTE" = "%@ minute";
"TIME_AMOUNT_MINUTES" = "%@ minutes";
"TIME_AMOUNT_SINGLE_HOUR" = "%@ hour";
"TIME_AMOUNT_HOURS" = "%@ hours";
"TIME_AMOUNT_SINGLE_DAY" = "%@ day";
"TIME_AMOUNT_DAYS" = "%@ days";
"TIME_AMOUNT_SINGLE_WEEK" = "%@ week";
"TIME_AMOUNT_WEEKS" = "%@ weeks";
"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@w";
"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@d";
"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@h";
"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@m";
"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@s";
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";

@ -88,239 +88,43 @@ extension String.StringInterpolation {
public extension String {
static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String {
let secondsPerMinute: TimeInterval = 60
let secondsPerHour: TimeInterval = (secondsPerMinute * 60)
let secondsPerDay: TimeInterval = (secondsPerHour * 24)
let secondsPerWeek: TimeInterval = (secondsPerDay * 7)
var dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.weekOfMonth, .day, .hour, .minute, .second]
var calendar = Calendar.current
switch format {
case .videoDuration:
let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
let hours: Int = Int(duration / 3600)
guard hours > 0 else { return String(format: "%02ld:%02ld", minutes, seconds) }
return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
guard duration < 3600 else { fallthrough }
dateComponentsFormatter.maximumUnitCount = 2
dateComponentsFormatter.unitsStyle = .positional
dateComponentsFormatter.zeroFormattingBehavior = .pad
return dateComponentsFormatter.string(from: duration) ?? ""
case .hoursMinutesSeconds:
let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
let hours: Int = Int(duration / 3600)
guard hours > 0 else { return String(format: "%ld:%02ld", minutes, seconds) }
return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
case .short:
switch duration {
case 0..<secondsPerMinute: // Seconds
return String(
format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration)),
number: .none
)
)
case secondsPerMinute..<secondsPerHour: // Minutes
return String(
format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case secondsPerHour..<secondsPerDay: // Hours
return String(
format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case secondsPerDay..<secondsPerWeek: // Days
return String(
format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
default: // Weeks
return String(
format: "TIME_AMOUNT_WEEKS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
}
case .long:
switch duration {
case 0..<secondsPerMinute: // XX Seconds
return String(
format: "TIME_AMOUNT_SECONDS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration)),
number: .none
)
)
case secondsPerMinute..<(secondsPerMinute * 1.5): // 1 Minute
return String(
format: "TIME_AMOUNT_SINGLE_MINUTE".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case (secondsPerMinute * 1.5)..<secondsPerHour: // Multiple Minutes
return String(
format: "TIME_AMOUNT_MINUTES".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case secondsPerHour..<(secondsPerHour * 1.5): // 1 Hour
return String(
format: "TIME_AMOUNT_SINGLE_HOUR".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case (secondsPerHour * 1.5)..<secondsPerDay: // Multiple Hours
return String(
format: "TIME_AMOUNT_HOURS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case secondsPerDay..<(secondsPerDay * 1.5): // 1 Day
return String(
format: "TIME_AMOUNT_SINGLE_DAY".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
case (secondsPerDay * 1.5)..<secondsPerWeek: // Multiple Days
return String(
format: "TIME_AMOUNT_DAYS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
case secondsPerWeek..<(secondsPerWeek * 1.5): // 1 Week
return String(
format: "TIME_AMOUNT_SINGLE_WEEK".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
default: // Multiple Weeks
return String(
format: "TIME_AMOUNT_WEEKS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
}
case .twoUnits:
let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
let hours: Int = Int((duration / 3600).truncatingRemainder(dividingBy: 24))
let days: Int = Int((duration / 3600 / 24).truncatingRemainder(dividingBy: 7))
let weeks: Int = Int(duration / 3600 / 24 / 7)
guard weeks == 0 else {
return String(
format: "TIME_AMOUNT_WEEKS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: weeks),
number: .none
)
) + " " + String(
format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: days),
number: .none
)
)
}
guard days == 0 else {
return String(
format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: days),
number: .none
)
) + " " + String(
format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: hours),
number: .none
)
)
}
guard hours == 0 else {
return String(
format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: hours),
number: .none
)
) + " " + String(
format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: minutes),
number: .none
)
)
}
guard minutes == 0 else {
return String(
format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: minutes),
number: .none
)
) + " " + String(
format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: seconds),
number: .none
)
)
}
return String(
format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(integerLiteral: seconds),
number: .none
)
)
dateComponentsFormatter.maximumUnitCount = 3
dateComponentsFormatter.unitsStyle = .positional
dateComponentsFormatter.zeroFormattingBehavior = .dropLeading
return dateComponentsFormatter.string(from: duration) ?? ""
case .short: // Single unit, no localization, short version e.g. 1w
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = .abbreviated
calendar.locale = Locale(identifier: "en-US")
dateComponentsFormatter.calendar = calendar
return dateComponentsFormatter.string(from: duration) ?? ""
case .long: // Single unit, long version e.g. 1 week
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = .full
return dateComponentsFormatter.string(from: duration) ?? ""
case .twoUnits: // 2 units, no localization, short version e.g 1w 1d
dateComponentsFormatter.maximumUnitCount = 2
dateComponentsFormatter.unitsStyle = .abbreviated
dateComponentsFormatter.zeroFormattingBehavior = .dropLeading
calendar.locale = Locale(identifier: "en-US")
dateComponentsFormatter.calendar = calendar
return dateComponentsFormatter.string(from: duration) ?? ""
}
}
}

Loading…
Cancel
Save