[Meta] Automatic String Organization (#1372)

* Automate String Organization.

* Comment the script so it's easier to maintain? Or messier?

* Linting post comments

* Rename ShellScript -> Alphabetize Strings for tvOS

* use swift regex, add error messages, clean up separators

* Only search for ./Translations/en.lproj/Localizable.strings

* Purge Unused Strings Script

* Organize Translation Scripts into a Folder. Update references at the project level.

* clean up

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
Joe Kribs 2024-12-21 00:01:11 -07:00 committed by GitHub
parent a6bd093960
commit af602d3d98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 332 additions and 113 deletions

View File

@ -0,0 +1,54 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Foundation
// Get the English localization file
let fileURL = URL(fileURLWithPath: "./Translations/en.lproj/Localizable.strings")
// This regular expression pattern matches lines of the format:
// "Key" = "Value";
let regex = #/^\"(?<key>[^\"]+)\"\s*=\s*\"(?<value>[^\"]+)\";/#
// Attempt to read the file content.
guard let content = try? String(contentsOf: fileURL, encoding: .utf8) else {
print("Unable to read file: \(fileURL.path)")
exit(1)
}
// Split file content by newlines to process line by line.
let strings = content.components(separatedBy: .newlines)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty && !$0.hasPrefix("//") }
let entries = strings.reduce(into: [String: String]()) {
if let match = $1.firstMatch(of: regex) {
let key = String(match.output.key)
let value = String(match.output.value)
$0[key] = value
} else {
print("Error: Invalid line format in \(fileURL.path): \($1)")
exit(1)
}
}
// Sort the keys alphabetically for consistent ordering.
let sortedKeys = entries.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
let newContent = sortedKeys.map { "/// \(entries[$0]!)\n\"\($0)\" = \"\(entries[$0]!)\";" }.joined(separator: "\n\n")
// Write the updated, sorted, and commented localizations back to the file.
do {
try newContent.write(to: fileURL, atomically: true, encoding: .utf8)
if let derivedFileDirectory = ProcessInfo.processInfo.environment["DERIVED_FILE_DIR"] {
try? "".write(toFile: derivedFileDirectory + "/alphabetizeStrings.txt", atomically: true, encoding: .utf8)
}
} catch {
print("Error: Failed to write to \(fileURL.path)")
exit(1)
}

View File

@ -0,0 +1,111 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Foundation
// Path to the English localization file
let localizationFile = "./Translations/en.lproj/Localizable.strings"
// Directories to scan for Swift files
let directoriesToScan = ["./Shared", "./Swiftfin", "./Swiftfin tvOS"]
// File to exclude from scanning
let excludedFile = "./Shared/Strings/Strings.swift"
// Regular expressions to match localization entries and usage in Swift files
// Matches lines like "Key" = "Value";
let localizationRegex = #/^\"(?<key>[^\"]+)\"\s*=\s*\"(?<value>[^\"]+)\";$/#
// Matches usage like L10n.key in Swift files
let usageRegex = #/L10n\.(?<key>[a-zA-Z0-9_]+)/#
// Attempt to load the localization file's content
guard let localizationContent = try? String(contentsOfFile: localizationFile, encoding: .utf8) else {
print("Unable to read localization file at \(localizationFile)")
exit(1)
}
// Split the file into lines and initialize a dictionary for localization entries
let localizationLines = localizationContent.components(separatedBy: .newlines)
var localizationEntries = [String: String]()
// Parse each line to extract key-value pairs
for line in localizationLines {
let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
// Skip empty lines or comments
if trimmed.isEmpty || trimmed.hasPrefix("//") { continue }
// Match valid localization entries and add them to the dictionary
if let match = line.firstMatch(of: localizationRegex) {
let key = String(match.output.key)
let value = String(match.output.value)
localizationEntries[key] = value
}
}
// Set to store all keys found in the codebase
var usedKeys = Set<String>()
// Function to scan a directory recursively for Swift files
func scanDirectory(_ path: String) {
let fileManager = FileManager.default
guard let enumerator = fileManager.enumerator(atPath: path) else { return }
for case let file as String in enumerator {
let filePath = "\(path)/\(file)"
// Skip the excluded file
if filePath == excludedFile { continue }
// Process only Swift files
if file.hasSuffix(".swift") {
if let fileContent = try? String(contentsOfFile: filePath, encoding: .utf8) {
for line in fileContent.components(separatedBy: .newlines) {
// Find all matches for L10n.key in each line
let matches = line.matches(of: usageRegex)
for match in matches {
let key = String(match.output.key)
usedKeys.insert(key)
}
}
}
}
}
}
// Scan all specified directories
for directory in directoriesToScan {
scanDirectory(directory)
}
// MARK: - Remove Unused Keys
// Identify keys in the localization file that are not used in the codebase
let unusedKeys = localizationEntries.keys.filter { !usedKeys.contains($0) }
// Remove unused keys from the dictionary
unusedKeys.forEach { localizationEntries.removeValue(forKey: $0) }
// MARK: - Write Updated Localizable.strings
// Sort keys alphabetically for consistent formatting
let sortedKeys = localizationEntries.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
// Reconstruct the localization file with sorted and updated entries
let updatedContent = sortedKeys.map { "/// \(localizationEntries[$0]!)\n\"\($0)\" = \"\(localizationEntries[$0]!)\";" }
.joined(separator: "\n\n")
// Attempt to write the updated content back to the localization file
do {
try updatedContent.write(toFile: localizationFile, atomically: true, encoding: .utf8)
print("Localization file updated. Removed \(unusedKeys.count) unused keys.")
} catch {
print("Error: Failed to write updated localization file.")
exit(1)
}

View File

@ -1346,8 +1346,6 @@ internal enum L10n {
internal static let weekly = L10n.tr("Localizable", "weekly", fallback: "Weekly")
/// This will be created as a new item on your Jellyfin Server.
internal static let willBeCreatedOnServer = L10n.tr("Localizable", "willBeCreatedOnServer", fallback: "This will be created as a new item on your Jellyfin Server.")
/// WIP
internal static let wip = L10n.tr("Localizable", "wip", fallback: "WIP")
/// Writer
internal static let writer = L10n.tr("Localizable", "writer", fallback: "Writer")
/// Year

View File

@ -762,7 +762,6 @@
E1763A292BF3046A004DF6AB /* AddUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A282BF3046A004DF6AB /* AddUserButton.swift */; };
E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */; };
E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */; };
E1763A662BF3CA83004DF6AB /* FullScreenMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */; };
E1763A6A2BF3D177004DF6AB /* PublicUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A692BF3D177004DF6AB /* PublicUserButton.swift */; };
E1763A712BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
@ -1272,6 +1271,7 @@
4E6C27072C8BD0AD00FD2185 /* ActiveSessionDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionDetailView.swift; sourceTree = "<group>"; };
4E71D6882C80910900A0174D /* EditCustomDeviceProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDeviceProfileView.swift; sourceTree = "<group>"; };
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
4E75B34A2D164AC100D16531 /* PurgeUnusedStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurgeUnusedStrings.swift; sourceTree = "<group>"; };
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.swift; sourceTree = "<group>"; };
4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreModal.swift; sourceTree = "<group>"; };
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
@ -1324,6 +1324,7 @@
4EC2B1A82CC97C0400D866BE /* ServerUserDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerUserDetailsView.swift; sourceTree = "<group>"; };
4EC50D602C934B3A00FC3D0E /* ServerTasksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksViewModel.swift; sourceTree = "<group>"; };
4EC6C16A2C92999800FC904B /* TranscodeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeSection.swift; sourceTree = "<group>"; };
4EC71FBB2D161FE300D0B3A8 /* AlphabetizeStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphabetizeStrings.swift; sourceTree = "<group>"; };
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeReason.swift; sourceTree = "<group>"; };
4ECF5D812D0A3D0200F066B1 /* AddAccessScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccessScheduleView.swift; sourceTree = "<group>"; };
4ECF5D892D0A57EF00F066B1 /* DynamicDayOfWeek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDayOfWeek.swift; sourceTree = "<group>"; };
@ -1695,7 +1696,6 @@
E1763A282BF3046A004DF6AB /* AddUserButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddUserButton.swift; sourceTree = "<group>"; };
E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGridButton.swift; sourceTree = "<group>"; };
E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowButton.swift; sourceTree = "<group>"; };
E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMenu.swift; sourceTree = "<group>"; };
E1763A692BF3D177004DF6AB /* PublicUserButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserButton.swift; sourceTree = "<group>"; };
E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftfinStore+Mappings.swift"; sourceTree = "<group>"; };
E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; };
@ -2462,6 +2462,15 @@
path = ActiveSessionDetailView;
sourceTree = "<group>";
};
4E75B34D2D16583900D16531 /* Translations */ = {
isa = PBXGroup;
children = (
4EC71FBB2D161FE300D0B3A8 /* AlphabetizeStrings.swift */,
4E75B34A2D164AC100D16531 /* PurgeUnusedStrings.swift */,
);
path = Translations;
sourceTree = "<group>";
};
4E8F74A32CE03D3100CC8969 /* ItemEditorView */ = {
isa = PBXGroup;
children = (
@ -2692,6 +2701,14 @@
path = ServerUserDetailsView;
sourceTree = "<group>";
};
4EC71FBA2D161FD800D0B3A8 /* Scripts */ = {
isa = PBXGroup;
children = (
4E75B34D2D16583900D16531 /* Translations */,
);
path = Scripts;
sourceTree = "<group>";
};
4ECF5D822D0A3D0200F066B1 /* AddAccessScheduleView */ = {
isa = PBXGroup;
children = (
@ -3004,6 +3021,7 @@
534D4FE126A7D7CC000A7A48 /* Translations */,
5377CBF2263B596A003A4E83 /* Products */,
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
4EC71FBA2D161FD800D0B3A8 /* Scripts */,
);
sourceTree = "<group>";
};
@ -4753,6 +4771,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 535870712669D21700D05A09 /* Build configuration list for PBXNativeTarget "Swiftfin tvOS" */;
buildPhases = (
4EC71FBD2D1620AF00D0B3A8 /* Alphabetize Strings */,
6286F0A3271C0ABA00C40ED5 /* Run Swiftgen.swift */,
BD83D7852B55EEB600652C24 /* Run SwiftFormat */,
5358705C2669D21600D05A09 /* Sources */,
@ -4800,6 +4819,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "Swiftfin iOS" */;
buildPhases = (
4EC71FBC2D16201C00D0B3A8 /* Alphabetize Strings */,
6286F09E271C093000C40ED5 /* Run Swiftgen.swift */,
BD0BA2282AD64BB200306A8D /* Run SwiftFormat */,
5377CBED263B596A003A4E83 /* Sources */,
@ -4989,6 +5009,46 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4EC71FBC2D16201C00D0B3A8 /* Alphabetize Strings */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(SRCROOT)/Translations/en.lproj/Localizable.strings",
);
name = "Alphabetize Strings";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/alphabetizeStrings.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "xcrun --sdk macosx swift \"${SRCROOT}/Scripts/Translations/AlphabetizeStrings.swift\"\n";
};
4EC71FBD2D1620AF00D0B3A8 /* Alphabetize Strings */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(SRCROOT)/Translations/en.lproj/Localizable.strings",
);
name = "Alphabetize Strings";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/alphabetizeStrings.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "xcrun --sdk macosx swift \"${SRCROOT}/Scripts/Translations/AlphabetizeStrings.swift\"\n";
};
6286F09E271C093000C40ED5 /* Run Swiftgen.swift */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;

View File

@ -13,6 +13,9 @@
/// Access
"access" = "Access";
/// Accessibility
"accessibility" = "Accessibility";
/// The End Time must come after the Start Time.
"accessScheduleInvalidTime" = "The End Time must come after the Start Time.";
@ -22,9 +25,6 @@
/// Define the allowed hours for usage and restrict access outside those times.
"accessSchedulesDescription" = "Define the allowed hours for usage and restrict access outside those times.";
/// Accessibility
"accessibility" = "Accessibility";
/// Active
"active" = "Active";
@ -37,11 +37,14 @@
/// Add
"add" = "Add";
/// Add Access Schedule
"addAccessSchedule" = "Add Access Schedule";
/// Add API key
"addAPIKey" = "Add API key";
/// Add Access Schedule
"addAccessSchedule" = "Add Access Schedule";
/// Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.
"additionalSecurityAccessDescription" = "Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.";
/// Add Server
"addServer" = "Add Server";
@ -55,9 +58,6 @@
/// Add User
"addUser" = "Add User";
/// Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.
"additionalSecurityAccessDescription" = "Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.";
/// Administrator
"administrator" = "Administrator";
@ -67,15 +67,15 @@
/// Age %@
"agesGroup" = "Age %@";
/// Aired
"aired" = "Aired";
/// Air Time
"airTime" = "Air Time";
/// Airs %s
"airWithDate" = "Airs %s";
/// Aired
"aired" = "Aired";
/// Album Artist
"albumArtist" = "Album Artist";
@ -88,12 +88,6 @@
/// All Media
"allMedia" = "All Media";
/// All Servers
"allServers" = "All Servers";
/// View and manage all registered users on the server, including their permissions and activity status.
"allUsersDescription" = "View and manage all registered users on the server, including their permissions and activity status.";
/// Allow collection management
"allowCollectionManagement" = "Allow collection management";
@ -103,6 +97,12 @@
/// Allow media item editing
"allowItemEditing" = "Allow media item editing";
/// All Servers
"allServers" = "All Servers";
/// View and manage all registered users on the server, including their permissions and activity status.
"allUsersDescription" = "View and manage all registered users on the server, including their permissions and activity status.";
/// Alternate
"alternate" = "Alternate";
@ -127,12 +127,12 @@
/// API Keys
"apiKeysTitle" = "API Keys";
/// App Icon
"appIcon" = "App Icon";
/// Appearance
"appearance" = "Appearance";
/// App Icon
"appIcon" = "App Icon";
/// Application Name
"applicationName" = "Application Name";
@ -202,12 +202,12 @@
/// Tests your server connection to assess internet speed and adjust bandwidth automatically.
"birateAutoDescription" = "Tests your server connection to assess internet speed and adjust bandwidth automatically.";
/// Birth year
"birthYear" = "Birth year";
/// Birthday
"birthday" = "Birthday";
/// Birth year
"birthYear" = "Birth year";
/// Auto
"bitrateAuto" = "Auto";
@ -316,12 +316,12 @@
/// Channels
"channels" = "Channels";
/// Chapter Slider
"chapterSlider" = "Chapter Slider";
/// Chapters
"chapters" = "Chapters";
/// Chapter Slider
"chapterSlider" = "Chapter Slider";
/// Cinematic
"cinematic" = "Cinematic";
@ -418,15 +418,15 @@
/// Cover Artist
"coverArtist" = "Cover Artist";
/// Create & Join Groups
"createAndJoinGroups" = "Create & Join Groups";
/// Create API Key
"createAPIKey" = "Create API Key";
/// Enter the application name for the new API key.
"createAPIKeyMessage" = "Enter the application name for the new API key.";
/// Create & Join Groups
"createAndJoinGroups" = "Create & Join Groups";
/// Create a pin to sign in to %@ on this device
"createPinForUser" = "Create a pin to sign in to %@ on this device";
@ -472,6 +472,9 @@
/// Custom failed logins
"customFailedLogins" = "Custom failed logins";
/// Customize
"customize" = "Customize";
/// Custom Profile
"customProfile" = "Custom Profile";
@ -481,9 +484,6 @@
/// Custom sessions
"customSessions" = "Custom sessions";
/// Customize
"customize" = "Customize";
/// Daily
"daily" = "Daily";
@ -637,6 +637,9 @@
/// Plays content in its original format. May cause playback issues on unsupported media types.
"directDescription" = "Plays content in its original format. May cause playback issues on unsupported media types.";
/// Director
"director" = "Director";
/// Direct Play
"directPlay" = "Direct Play";
@ -646,9 +649,6 @@
/// Direct Stream
"directStream" = "Direct Stream";
/// Director
"director" = "Director";
/// Disabled
"disabled" = "Disabled";
@ -679,15 +679,15 @@
/// Edit
"edit" = "Edit";
/// Editor
"editor" = "Editor";
/// Edit Server
"editServer" = "Edit Server";
/// Edit Users
"editUsers" = "Edit Users";
/// Editor
"editor" = "Editor";
/// Enable all devices
"enableAllDevices" = "Enable all devices";
@ -700,12 +700,12 @@
/// End Date
"endDate" = "End Date";
/// End Time
"endTime" = "End Time";
/// Ended
"ended" = "Ended";
/// End Time
"endTime" = "End Time";
/// Engineer
"engineer" = "Engineer";
@ -754,12 +754,12 @@
/// Every
"every" = "Every";
/// Every %1$@
"everyInterval" = "Every %1$@";
/// Everyday
"everyday" = "Everyday";
/// Every %1$@
"everyInterval" = "Every %1$@";
/// Executed
"executed" = "Executed";
@ -937,12 +937,12 @@
/// Letter
"letter" = "Letter";
/// Letter Picker
"letterPicker" = "Letter Picker";
/// Letterer
"letterer" = "Letterer";
/// Letter Picker
"letterPicker" = "Letter Picker";
/// Library
"library" = "Library";
@ -958,15 +958,15 @@
/// Live TV
"liveTV" = "Live TV";
/// Live TV access
"liveTvAccess" = "Live TV access";
/// Live TV Channels
"liveTVChannels" = "Live TV Channels";
/// Live TV Programs
"liveTVPrograms" = "Live TV Programs";
/// Live TV access
"liveTvAccess" = "Live TV access";
/// Live TV recording management
"liveTvRecordingManagement" = "Live TV recording management";
@ -1000,12 +1000,6 @@
/// Management
"management" = "Management";
/// Maximum parental rating
"maxParentalRating" = "Maximum parental rating";
/// Content with a higher rating will be hidden from this user.
"maxParentalRatingDescription" = "Content with a higher rating will be hidden from this user.";
/// Maximum Bitrate
"maximumBitrate" = "Maximum Bitrate";
@ -1030,6 +1024,12 @@
/// Maximum sessions policy
"maximumSessionsPolicy" = "Maximum sessions policy";
/// Maximum parental rating
"maxParentalRating" = "Maximum parental rating";
/// Content with a higher rating will be hidden from this user.
"maxParentalRatingDescription" = "Content with a higher rating will be hidden from this user.";
/// Media
"media" = "Media";
@ -1096,12 +1096,12 @@
/// New Password
"newPassword" = "New Password";
/// New User
"newUser" = "New User";
/// News
"news" = "News";
/// New User
"newUser" = "New User";
/// Next
"next" = "Next";
@ -1129,6 +1129,9 @@
/// No local servers found
"noLocalServersFound" = "No local servers found";
/// None
"none" = "None";
/// No overview available
"noOverviewAvailable" = "No overview available";
@ -1138,24 +1141,21 @@
/// No results.
"noResults" = "No results.";
/// Normal
"normal" = "Normal";
/// No runtime limit
"noRuntimeLimit" = "No runtime limit";
/// No session
"noSession" = "No session";
/// No title
"noTitle" = "No title";
/// None
"none" = "None";
/// Normal
"normal" = "Normal";
/// Type: %@ not implemented yet :(
"notImplementedYetWithType" = "Type: %@ not implemented yet :(";
/// No title
"noTitle" = "No title";
/// Official Rating
"officialRating" = "Official Rating";
@ -1207,12 +1207,12 @@
/// Password
"password" = "Password";
/// Changes the Jellyfin server user password. This does not change any Swiftfin settings.
"passwordChangeWarning" = "Changes the Jellyfin server user password. This does not change any Swiftfin settings.";
/// User password has been changed.
"passwordChangedMessage" = "User password has been changed.";
/// Changes the Jellyfin server user password. This does not change any Swiftfin settings.
"passwordChangeWarning" = "Changes the Jellyfin server user password. This does not change any Swiftfin settings.";
/// New passwords do not match.
"passwordsDoNotMatch" = "New passwords do not match.";
@ -1240,18 +1240,6 @@
/// Play / Pause
"playAndPause" = "Play / Pause";
/// Play From Beginning
"playFromBeginning" = "Play From Beginning";
/// Play Next Item
"playNextItem" = "Play Next Item";
/// Play on active
"playOnActive" = "Play on active";
/// Play Previous Item
"playPreviousItem" = "Play Previous Item";
/// Playback
"playback" = "Playback";
@ -1267,6 +1255,18 @@
/// Played
"played" = "Played";
/// Play From Beginning
"playFromBeginning" = "Play From Beginning";
/// Play Next Item
"playNextItem" = "Play Next Item";
/// Play on active
"playOnActive" = "Play on active";
/// Play Previous Item
"playPreviousItem" = "Play Previous Item";
/// Posters
"posters" = "Posters";
@ -1402,6 +1402,9 @@
/// Replace unlocked metadata with new information.
"replaceMetadataDescription" = "Replace unlocked metadata with new information.";
/// Required
"required" = "Required";
/// Require device authentication when signing in to the user.
"requireDeviceAuthDescription" = "Require device authentication when signing in to the user.";
@ -1414,9 +1417,6 @@
/// Require a local pin when signing in to the user. This pin is unrecoverable.
"requirePinDescription" = "Require a local pin when signing in to the user. This pin is unrecoverable.";
/// Required
"required" = "Required";
/// Reset
"reset" = "Reset";
@ -1525,12 +1525,12 @@
/// Server Logs
"serverLogs" = "Server Logs";
/// Server URL
"serverURL" = "Server URL";
/// Servers
"servers" = "Servers";
/// Server URL
"serverURL" = "Server URL";
/// Session
"session" = "Session";
@ -1660,15 +1660,15 @@
/// Subtitle Offset
"subtitleOffset" = "Subtitle Offset";
/// Subtitle Size
"subtitleSize" = "Subtitle Size";
/// Subtitles
"subtitles" = "Subtitles";
/// Settings only affect some subtitle types
"subtitlesDisclaimer" = "Settings only affect some subtitle types";
/// Subtitle Size
"subtitleSize" = "Subtitle Size";
/// Success
"success" = "Success";
@ -1720,18 +1720,18 @@
/// Failed
"taskFailed" = "Failed";
/// Sets the duration (in minutes) in between task triggers.
"taskTriggerInterval" = "Sets the duration (in minutes) in between task triggers.";
/// Sets the maximum runtime (in hours) for this task trigger.
"taskTriggerTimeLimit" = "Sets the maximum runtime (in hours) for this task trigger.";
/// Tasks
"tasks" = "Tasks";
/// Tasks are operations that are scheduled to run periodically or can be triggered manually.
"tasksDescription" = "Tasks are operations that are scheduled to run periodically or can be triggered manually.";
/// Sets the duration (in minutes) in between task triggers.
"taskTriggerInterval" = "Sets the duration (in minutes) in between task triggers.";
/// Sets the maximum runtime (in hours) for this task trigger.
"taskTriggerTimeLimit" = "Sets the maximum runtime (in hours) for this task trigger.";
/// Tbps
"terabitsPerSecond" = "Tbps";
@ -1858,18 +1858,18 @@
/// This user will require device authentication.
"userDeviceAuthRequiredDescription" = "This user will require device authentication.";
/// This user will require a pin.
"userPinRequiredDescription" = "This user will require a pin.";
/// User %@ requires device authentication
"userRequiresDeviceAuthentication" = "User %@ requires device authentication";
/// Username
"username" = "Username";
/// A username is required
"usernameRequired" = "A username is required";
/// This user will require a pin.
"userPinRequiredDescription" = "This user will require a pin.";
/// User %@ requires device authentication
"userRequiresDeviceAuthentication" = "User %@ requires device authentication";
/// Users
"users" = "Users";
@ -1927,9 +1927,6 @@
/// This will be created as a new item on your Jellyfin Server.
"willBeCreatedOnServer" = "This will be created as a new item on your Jellyfin Server.";
/// WIP
"wip" = "WIP";
/// Writer
"writer" = "Writer";
@ -1944,4 +1941,3 @@
/// Yes
"yes" = "Yes";