german and spanish translation
This commit is contained in:
@@ -205,6 +205,8 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
de,
|
||||
es,
|
||||
);
|
||||
mainGroup = 3E5C0BC32E72C0FD00247EC8;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
|
||||
BIN
Cable/AppIcon.icon/Assets/box-2.png
Normal file
BIN
Cable/AppIcon.icon/Assets/box-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
37
Cable/Base.lproj/Localizable.strings
Normal file
37
Cable/Base.lproj/Localizable.strings
Normal file
@@ -0,0 +1,37 @@
|
||||
"affiliate.button.review_parts" = "Review parts";
|
||||
"affiliate.description.with_link" = "Tapping above shows a full bill of materials before opening the affiliate link. Purchases may support VoltPlan.";
|
||||
"affiliate.description.without_link" = "Tapping above shows a full bill of materials with shopping searches to help you source parts.";
|
||||
"affiliate.disclaimer" = "Purchases through affiliate links may support VoltPlan.";
|
||||
"bom.accessibility.mark.complete" = "Mark %@ complete";
|
||||
"bom.accessibility.mark.incomplete" = "Mark %@ incomplete";
|
||||
"bom.fuse.detail" = "Inline holder and %dA fuse";
|
||||
"bom.item.cable.black" = "Power Cable (Black)";
|
||||
"bom.item.cable.red" = "Power Cable (Red)";
|
||||
"bom.item.fuse" = "Fuse & Holder";
|
||||
"bom.item.terminals" = "Cable Shoes / Terminals";
|
||||
"bom.navigation.title" = "Bill of Materials";
|
||||
"bom.navigation.title.system" = "BOM – %@";
|
||||
"bom.size.unknown" = "Size TBD";
|
||||
"bom.terminals.detail" = "Ring or spade terminals sized for %@ wiring";
|
||||
"component.fallback.name" = "Component";
|
||||
"default.load.library" = "Library Load";
|
||||
"default.load.name" = "My Load";
|
||||
"default.load.unnamed" = "Unnamed Load";
|
||||
"default.load.new" = "New Load";
|
||||
"default.system.name" = "My System";
|
||||
"default.system.new" = "New System";
|
||||
"editor.load.name_field" = "Load name";
|
||||
"editor.load.preview" = "Preview";
|
||||
"editor.load.title" = "Edit Load";
|
||||
"editor.system.location.optional" = "Location (optional)";
|
||||
"editor.system.name_field" = "System name";
|
||||
"editor.system.title" = "Edit System";
|
||||
"slider.button.ampere" = "Ampere";
|
||||
"slider.button.watt" = "Watt";
|
||||
"slider.current.title" = "Current";
|
||||
"slider.length.title" = "Cable Length (%@)";
|
||||
"slider.power.title" = "Power";
|
||||
"slider.voltage.title" = "Voltage";
|
||||
"system.list.no.components" = "No components yet";
|
||||
"units.imperial.display" = "Imperial (AWG, ft)";
|
||||
"units.metric.display" = "Metric (mm², m)";
|
||||
22
Cable/Base.lproj/Localizable.stringsdict
Normal file
22
Cable/Base.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>system.list.component.summary</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@component_count@ • %@</string>
|
||||
<key>component_count</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d component</string>
|
||||
<key>other</key>
|
||||
<string>%d components</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -13,7 +13,7 @@ class CableCalculator: ObservableObject {
|
||||
@Published var current: Double = 5.0
|
||||
@Published var power: Double = 60.0
|
||||
@Published var length: Double = 10.0
|
||||
@Published var loadName: String = "My Load"
|
||||
@Published var loadName: String = String(localized: "default.load.name", comment: "Default placeholder name for a load")
|
||||
|
||||
var calculatedPower: Double {
|
||||
voltage * current
|
||||
|
||||
@@ -241,7 +241,7 @@ struct CalculatorView: View {
|
||||
let countryCode = rawCountryCode?.uppercased()
|
||||
let regionName = countryCode.flatMap { Locale.current.localizedString(forRegionCode: $0) ?? $0 }
|
||||
|
||||
let buttonTitle = "Review parts"
|
||||
let buttonTitle = String(localized: "affiliate.button.review_parts", comment: "Button title to review a bill of materials before shopping")
|
||||
let identifier = "bom-\(savedLoad.name)-\(savedLoad.timestamp.timeIntervalSince1970)"
|
||||
|
||||
return AffiliateLinkInfo(
|
||||
@@ -520,7 +520,14 @@ struct CalculatorView: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(info.affiliateURL != nil ? "Tapping above shows a full bill of materials before opening the affiliate link. Purchases may support VoltPlan." : "Tapping above shows a full bill of materials with shopping searches to help you source parts.")
|
||||
let descriptionKey = info.affiliateURL != nil
|
||||
? "affiliate.description.with_link"
|
||||
: "affiliate.description.without_link"
|
||||
let description = NSLocalizedString(
|
||||
descriptionKey,
|
||||
comment: "Explanation text beneath the affiliate button"
|
||||
)
|
||||
Text(description)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -537,19 +544,36 @@ struct CalculatorView: View {
|
||||
|
||||
let crossSectionValue = calculator.crossSection(for: unitSystem)
|
||||
let crossSectionLabel: String
|
||||
let unknownSizeLabel = String(localized: "bom.size.unknown", comment: "Fallback label when the cable size is unknown")
|
||||
if unitSystem == .imperial {
|
||||
crossSectionLabel = String(format: "AWG %.0f", crossSectionValue)
|
||||
if crossSectionValue > 0 {
|
||||
crossSectionLabel = String(format: "AWG %.0f", crossSectionValue)
|
||||
} else {
|
||||
crossSectionLabel = unknownSizeLabel
|
||||
}
|
||||
} else {
|
||||
crossSectionLabel = String(format: "%.1f mm²", crossSectionValue)
|
||||
if crossSectionValue > 0 {
|
||||
crossSectionLabel = String(format: "%.1f mm²", crossSectionValue)
|
||||
} else {
|
||||
crossSectionLabel = unknownSizeLabel
|
||||
}
|
||||
}
|
||||
|
||||
let cableDetail = "\(lengthLabel) • \(crossSectionLabel)"
|
||||
let powerDetail = String(format: "%.0f W @ %.1f V", calculator.calculatedPower, calculator.voltage)
|
||||
|
||||
let fuseRating = calculator.recommendedFuse
|
||||
let fuseDetail = "Inline holder and \(fuseRating)A fuse"
|
||||
let fuseDetailFormat = NSLocalizedString(
|
||||
"bom.fuse.detail",
|
||||
comment: "Description for the fuse entry in the calculator BOM"
|
||||
)
|
||||
let fuseDetail = String.localizedStringWithFormat(fuseDetailFormat, fuseRating)
|
||||
|
||||
let cableShoesDetail = "Ring or spade terminals sized for \(crossSectionLabel) wiring"
|
||||
let cableShoesDetailFormat = NSLocalizedString(
|
||||
"bom.terminals.detail",
|
||||
comment: "Description for the cable terminals entry in the calculator BOM"
|
||||
)
|
||||
let cableShoesDetail = String.localizedStringWithFormat(cableShoesDetailFormat, crossSectionLabel.lowercased())
|
||||
|
||||
let cableGaugeQuery: String
|
||||
if unitSystem == .imperial {
|
||||
@@ -568,10 +592,12 @@ struct CalculatorView: View {
|
||||
|
||||
var items: [BOMItem] = []
|
||||
|
||||
let fallbackComponentTitle = String(localized: "component.fallback.name", comment: "Fallback name for a component when no custom name is provided")
|
||||
|
||||
items.append(
|
||||
BOMItem(
|
||||
id: "component",
|
||||
title: calculator.loadName.isEmpty ? "Component" : calculator.loadName,
|
||||
title: calculator.loadName.isEmpty ? fallbackComponentTitle : calculator.loadName,
|
||||
detail: powerDetail,
|
||||
iconSystemName: "bolt.fill",
|
||||
destination: deviceLink.map { .affiliate($0) } ?? .amazonSearch(deviceQueryBase),
|
||||
@@ -582,7 +608,7 @@ struct CalculatorView: View {
|
||||
items.append(
|
||||
BOMItem(
|
||||
id: "cable-red",
|
||||
title: "Power Cable (Red)",
|
||||
title: String(localized: "bom.item.cable.red", comment: "Title for the red power cable item"),
|
||||
detail: cableDetail,
|
||||
iconSystemName: "bolt.horizontal.circle",
|
||||
destination: .amazonSearch(redCableQuery),
|
||||
@@ -593,7 +619,7 @@ struct CalculatorView: View {
|
||||
items.append(
|
||||
BOMItem(
|
||||
id: "cable-black",
|
||||
title: "Power Cable (Black)",
|
||||
title: String(localized: "bom.item.cable.black", comment: "Title for the black power cable item"),
|
||||
detail: cableDetail,
|
||||
iconSystemName: "bolt.horizontal.circle",
|
||||
destination: .amazonSearch(blackCableQuery),
|
||||
@@ -604,7 +630,7 @@ struct CalculatorView: View {
|
||||
items.append(
|
||||
BOMItem(
|
||||
id: "fuse",
|
||||
title: "Fuse & Holder",
|
||||
title: String(localized: "bom.item.fuse", comment: "Title for the fuse item"),
|
||||
detail: fuseDetail,
|
||||
iconSystemName: "bolt.shield",
|
||||
destination: .amazonSearch(fuseQuery),
|
||||
@@ -615,7 +641,7 @@ struct CalculatorView: View {
|
||||
items.append(
|
||||
BOMItem(
|
||||
id: "terminals",
|
||||
title: "Cable Shoes / Terminals",
|
||||
title: String(localized: "bom.item.terminals", comment: "Title for the terminals item"),
|
||||
detail: cableShoesDetail,
|
||||
iconSystemName: "wrench.and.screwdriver",
|
||||
destination: .amazonSearch(terminalQuery),
|
||||
@@ -636,7 +662,7 @@ struct CalculatorView: View {
|
||||
}
|
||||
|
||||
private var voltageSlider: some View {
|
||||
SliderSection(title: "Voltage",
|
||||
SliderSection(title: String(localized: "slider.voltage.title", comment: "Title for the voltage slider"),
|
||||
value: $calculator.voltage,
|
||||
range: 3...48,
|
||||
unit: "V",
|
||||
@@ -656,11 +682,11 @@ struct CalculatorView: View {
|
||||
@ViewBuilder
|
||||
private var currentPowerSlider: some View {
|
||||
if isWattMode {
|
||||
SliderSection(title: "Power",
|
||||
SliderSection(title: String(localized: "slider.power.title", comment: "Title for the power slider"),
|
||||
value: $calculator.power,
|
||||
range: 0...2000,
|
||||
unit: "W",
|
||||
buttonText: "Watt",
|
||||
buttonText: String(localized: "slider.button.watt", comment: "Button label when showing power control"),
|
||||
buttonAction: {
|
||||
isWattMode = false
|
||||
calculator.updateFromPower()
|
||||
@@ -674,11 +700,11 @@ struct CalculatorView: View {
|
||||
autoUpdateSavedLoad()
|
||||
}
|
||||
} else {
|
||||
SliderSection(title: "Current",
|
||||
SliderSection(title: String(localized: "slider.current.title", comment: "Title for the current slider"),
|
||||
value: $calculator.current,
|
||||
range: 0...100,
|
||||
unit: "A",
|
||||
buttonText: "Ampere",
|
||||
buttonText: String(localized: "slider.button.ampere", comment: "Button label when showing current control"),
|
||||
buttonAction: {
|
||||
isWattMode = true
|
||||
calculator.updateFromCurrent()
|
||||
@@ -695,7 +721,11 @@ struct CalculatorView: View {
|
||||
}
|
||||
|
||||
private var lengthSlider: some View {
|
||||
SliderSection(title: "Cable Length (\(unitSettings.unitSystem.lengthUnit))",
|
||||
let lengthTitleFormat = NSLocalizedString(
|
||||
"slider.length.title",
|
||||
comment: "Title format for the cable length slider"
|
||||
)
|
||||
return SliderSection(title: String(format: lengthTitleFormat, locale: Locale.current, unitSettings.unitSystem.lengthUnit),
|
||||
value: $calculator.length,
|
||||
range: 0...20,
|
||||
unit: unitSettings.unitSystem.lengthUnit,
|
||||
@@ -711,8 +741,9 @@ struct CalculatorView: View {
|
||||
|
||||
|
||||
private func saveCurrentLoad() {
|
||||
let fallbackName = String(localized: "default.load.unnamed", comment: "Fallback name for a load when no name is provided")
|
||||
let savedLoad = SavedLoad(
|
||||
name: calculator.loadName.isEmpty ? "Unnamed Load" : calculator.loadName,
|
||||
name: calculator.loadName.isEmpty ? fallbackName : calculator.loadName,
|
||||
voltage: calculator.voltage,
|
||||
current: calculator.current,
|
||||
power: calculator.power,
|
||||
@@ -740,7 +771,8 @@ struct CalculatorView: View {
|
||||
private func autoUpdateSavedLoad() {
|
||||
guard let savedLoad = savedLoad else { return }
|
||||
|
||||
savedLoad.name = calculator.loadName.isEmpty ? "Unnamed Load" : calculator.loadName
|
||||
let fallbackName = String(localized: "default.load.unnamed", comment: "Fallback name for a load when no name is provided")
|
||||
savedLoad.name = calculator.loadName.isEmpty ? fallbackName : calculator.loadName
|
||||
savedLoad.voltage = calculator.voltage
|
||||
savedLoad.current = calculator.current
|
||||
savedLoad.power = calculator.power
|
||||
@@ -776,6 +808,21 @@ private struct BillOfMaterialsView: View {
|
||||
ForEach(items) { item in
|
||||
let isCompleted = completedItemIDs.contains(item.id)
|
||||
let destinationURL = destinationURL(for: item)
|
||||
let accessibilityLabel: String = {
|
||||
if isCompleted {
|
||||
let format = NSLocalizedString(
|
||||
"bom.accessibility.mark.incomplete",
|
||||
comment: "Accessibility label to mark a BOM item incomplete"
|
||||
)
|
||||
return String.localizedStringWithFormat(format, item.title)
|
||||
} else {
|
||||
let format = NSLocalizedString(
|
||||
"bom.accessibility.mark.complete",
|
||||
comment: "Accessibility label to mark a BOM item complete"
|
||||
)
|
||||
return String.localizedStringWithFormat(format, item.title)
|
||||
}
|
||||
}()
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
|
||||
@@ -789,7 +836,7 @@ private struct BillOfMaterialsView: View {
|
||||
}
|
||||
suppressRowTapForID = item.id
|
||||
}
|
||||
.accessibilityLabel(isCompleted ? "Mark \(item.title) incomplete" : "Mark \(item.title) complete")
|
||||
.accessibilityLabel(accessibilityLabel)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(item.title)
|
||||
@@ -798,7 +845,7 @@ private struct BillOfMaterialsView: View {
|
||||
.strikethrough(isCompleted, color: .accentColor.opacity(0.6))
|
||||
|
||||
if item.isPrimaryComponent {
|
||||
Text("Component")
|
||||
Text(String(localized: "component.fallback.name", comment: "Tag label marking a BOM entry as the main component"))
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.horizontal, 6)
|
||||
@@ -840,14 +887,24 @@ private struct BillOfMaterialsView: View {
|
||||
}
|
||||
|
||||
Section {
|
||||
Text("Purchases through affiliate links may support VoltPlan.")
|
||||
Text(
|
||||
String(
|
||||
localized: "affiliate.disclaimer",
|
||||
comment: "Footer note reminding users that affiliate purchases may support the app"
|
||||
)
|
||||
)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle("Bill of Materials")
|
||||
.navigationTitle(
|
||||
String(
|
||||
localized: "bom.navigation.title",
|
||||
comment: "Navigation title for the bill of materials view"
|
||||
)
|
||||
)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
|
||||
120
Cable/ComponentsOnboardingView.swift
Normal file
120
Cable/ComponentsOnboardingView.swift
Normal file
@@ -0,0 +1,120 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ComponentsOnboardingView: View {
|
||||
@State private var carouselStep = 0
|
||||
let onCreate: () -> Void
|
||||
let onBrowse: () -> Void
|
||||
|
||||
private let imageNames = [
|
||||
"fridge-onboarding",
|
||||
"coffee-onboarding",
|
||||
"light-onboarding"
|
||||
]
|
||||
|
||||
private let timer = Timer.publish(every: 8, on: .main, in: .common).autoconnect()
|
||||
private let animationDuration = 0.8
|
||||
|
||||
private var loopingImages: [String] {
|
||||
guard let first = imageNames.first else { return [] }
|
||||
return imageNames + [first]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer(minLength: 32)
|
||||
|
||||
OnboardingCarouselView(images: loopingImages, step: carouselStep)
|
||||
.frame(minHeight: 80, maxHeight: 240)
|
||||
.padding(.horizontal, 0)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Text("Add your first component")
|
||||
.font(.title2.weight(.semibold))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("Bring your system to life with components and let **Cable by VoltPlan** handle cable and fuse recommendations.")
|
||||
.font(.body)
|
||||
.foregroundStyle(Color.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(minHeight: 72)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Button(action: createComponent) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.system(size: 16))
|
||||
Text("Create Component")
|
||||
.font(.headline.weight(.semibold))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 48)
|
||||
.background(Color.blue)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Button(action: onBrowse) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "books.vertical")
|
||||
.font(.system(size: 16))
|
||||
Text("Browse Library")
|
||||
.font(.headline.weight(.semibold))
|
||||
}
|
||||
.foregroundColor(.blue)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 48)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color.blue.opacity(0.12))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.stroke(Color.blue.opacity(0.24), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
.padding(.bottom, 32)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.onAppear(perform: resetState)
|
||||
.onReceive(timer) { _ in advanceCarousel() }
|
||||
}
|
||||
|
||||
private func resetState() {
|
||||
carouselStep = 0
|
||||
}
|
||||
|
||||
private func createComponent() {
|
||||
onCreate()
|
||||
}
|
||||
|
||||
private func advanceCarousel() {
|
||||
guard imageNames.count > 1 else { return }
|
||||
let next = carouselStep + 1
|
||||
|
||||
withAnimation(.easeInOut(duration: animationDuration)) {
|
||||
carouselStep = next
|
||||
}
|
||||
|
||||
if next == imageNames.count {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) {
|
||||
withAnimation(.none) {
|
||||
carouselStep = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ComponentsOnboardingView(onCreate: {}, onBrowse: {})
|
||||
}
|
||||
@@ -192,7 +192,7 @@ struct SystemsView: View {
|
||||
private func makeSystem(preferredName: String? = nil, colorName: String? = nil, iconName: String? = nil) -> ElectricalSystem {
|
||||
let existingNames = Set(systems.map { $0.name })
|
||||
let trimmedPreferred = preferredName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let baseName = trimmedPreferred.isEmpty ? "New System" : trimmedPreferred
|
||||
let baseName = trimmedPreferred.isEmpty ? String(localized: "default.system.new", comment: "Default name for a newly created system") : trimmedPreferred
|
||||
var systemName = baseName
|
||||
var counter = 2
|
||||
|
||||
@@ -221,7 +221,7 @@ struct SystemsView: View {
|
||||
}
|
||||
|
||||
private func createLoad(from item: ComponentLibraryItem, in system: ElectricalSystem) -> SavedLoad {
|
||||
let baseName = item.name.isEmpty ? "Library Load" : item.name
|
||||
let baseName = item.name.isEmpty ? String(localized: "default.load.library", comment: "Default name when importing a library load") : item.name
|
||||
let loadName = uniqueLoadName(for: system, startingWith: baseName)
|
||||
let voltage = item.displayVoltage ?? 12.0
|
||||
|
||||
@@ -310,7 +310,9 @@ struct SystemsView: View {
|
||||
|
||||
private func componentSummary(for system: ElectricalSystem) -> String {
|
||||
let systemLoads = loads(for: system)
|
||||
guard !systemLoads.isEmpty else { return "No components yet" }
|
||||
guard !systemLoads.isEmpty else {
|
||||
return String(localized: "system.list.no.components", comment: "Message shown when a system has no components yet")
|
||||
}
|
||||
|
||||
let count = systemLoads.count
|
||||
let totalPower = systemLoads.reduce(0.0) { $0 + $1.power }
|
||||
@@ -322,7 +324,11 @@ struct SystemsView: View {
|
||||
formattedPower = String(format: "%.0fW", totalPower)
|
||||
}
|
||||
|
||||
return "\(count) component\(count == 1 ? "" : "s") • \(formattedPower) total"
|
||||
let format = NSLocalizedString(
|
||||
"system.list.component.summary",
|
||||
comment: "Summary showing number of components and the total power"
|
||||
)
|
||||
return String.localizedStringWithFormat(format, count, formattedPower)
|
||||
}
|
||||
|
||||
private func randomSystemColorName() -> String {
|
||||
@@ -622,7 +628,8 @@ struct LoadsView: View {
|
||||
}
|
||||
|
||||
private func createNewLoad() {
|
||||
let loadName = uniqueLoadName(startingWith: "New Load")
|
||||
let defaultName = String(localized: "default.load.new", comment: "Default name when creating a new load from system view")
|
||||
let loadName = uniqueLoadName(startingWith: defaultName)
|
||||
let newLoad = SavedLoad(
|
||||
name: loadName,
|
||||
voltage: 12.0,
|
||||
@@ -903,6 +910,22 @@ private struct SystemBillOfMaterialsView: View {
|
||||
let destinationURL = destinationURL(for: item.destination, load: load)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
let accessibilityLabel: String = {
|
||||
if isCompleted {
|
||||
let format = NSLocalizedString(
|
||||
"bom.accessibility.mark.incomplete",
|
||||
comment: "Accessibility label instructing VoiceOver to mark an item incomplete"
|
||||
)
|
||||
return String.localizedStringWithFormat(format, item.title)
|
||||
} else {
|
||||
let format = NSLocalizedString(
|
||||
"bom.accessibility.mark.complete",
|
||||
comment: "Accessibility label instructing VoiceOver to mark an item complete"
|
||||
)
|
||||
return String.localizedStringWithFormat(format, item.title)
|
||||
}
|
||||
}()
|
||||
|
||||
Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(isCompleted ? .accentColor : .secondary)
|
||||
.imageScale(.large)
|
||||
@@ -910,7 +933,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
setCompletion(!isCompleted, for: load, item: item)
|
||||
suppressRowTapForID = item.id
|
||||
}
|
||||
.accessibilityLabel(isCompleted ? "Mark \(item.title) incomplete" : "Mark \(item.title) complete")
|
||||
.accessibilityLabel(accessibilityLabel)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(item.title)
|
||||
@@ -919,7 +942,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
.strikethrough(isCompleted, color: .accentColor.opacity(0.6))
|
||||
|
||||
if item.isPrimaryComponent {
|
||||
Text("Component")
|
||||
Text(String(localized: "component.fallback.name", comment: "Tag label marking an item as the component itself"))
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.horizontal, 6)
|
||||
@@ -971,7 +994,16 @@ private struct SystemBillOfMaterialsView: View {
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle("BOM – \(systemName)")
|
||||
.navigationTitle(
|
||||
String(
|
||||
format: NSLocalizedString(
|
||||
"bom.navigation.title.system",
|
||||
comment: "Navigation title for the bill of materials view"
|
||||
),
|
||||
locale: Locale.current,
|
||||
systemName
|
||||
)
|
||||
)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
@@ -995,7 +1027,8 @@ private struct SystemBillOfMaterialsView: View {
|
||||
|
||||
private func sectionHeader(for load: SavedLoad) -> some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(load.name.isEmpty ? "Component" : load.name)
|
||||
let fallbackTitle = String(localized: "component.fallback.name", comment: "Fallback title for a component that lacks a name")
|
||||
Text(load.name.isEmpty ? fallbackTitle : load.name)
|
||||
.font(.headline)
|
||||
Text(dateFormatter.string(from: load.timestamp))
|
||||
.font(.caption)
|
||||
@@ -1014,13 +1047,15 @@ private struct SystemBillOfMaterialsView: View {
|
||||
|
||||
let crossSectionLabel: String
|
||||
let gaugeQuery: String
|
||||
let unknownSizeLabel = String(localized: "bom.size.unknown", comment: "Fallback label when the cable size is not yet determined")
|
||||
|
||||
if unitSystem == .imperial {
|
||||
let awg = awgFromCrossSection(load.crossSection)
|
||||
if awg > 0 {
|
||||
crossSectionLabel = String(format: "AWG %.0f", awg)
|
||||
gaugeQuery = String(format: "AWG %.0f", awg)
|
||||
} else {
|
||||
crossSectionLabel = "Size TBD"
|
||||
crossSectionLabel = unknownSizeLabel
|
||||
gaugeQuery = "battery cable"
|
||||
}
|
||||
} else {
|
||||
@@ -1028,7 +1063,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
crossSectionLabel = String(format: "%.1f mm²", load.crossSection)
|
||||
gaugeQuery = String(format: "%.1f mm2", load.crossSection)
|
||||
} else {
|
||||
crossSectionLabel = "Size TBD"
|
||||
crossSectionLabel = unknownSizeLabel
|
||||
gaugeQuery = "battery cable"
|
||||
}
|
||||
}
|
||||
@@ -1039,9 +1074,17 @@ private struct SystemBillOfMaterialsView: View {
|
||||
let powerDetail = String(format: "%.0f W @ %.1f V", calculatedPower, load.voltage)
|
||||
|
||||
let fuseRating = recommendedFuse(for: load)
|
||||
let fuseDetail = "Inline holder and \(fuseRating)A fuse"
|
||||
let fuseDetailFormat = NSLocalizedString(
|
||||
"bom.fuse.detail",
|
||||
comment: "Description for the fuse item in the BOM list"
|
||||
)
|
||||
let fuseDetail = String.localizedStringWithFormat(fuseDetailFormat, fuseRating)
|
||||
|
||||
let cableShoesDetail = "Ring or spade terminals sized for \(crossSectionLabel.lowercased()) wiring"
|
||||
let cableShoesDetailFormat = NSLocalizedString(
|
||||
"bom.terminals.detail",
|
||||
comment: "Description for the cable terminals item in the BOM list"
|
||||
)
|
||||
let cableShoesDetail = String.localizedStringWithFormat(cableShoesDetailFormat, crossSectionLabel.lowercased())
|
||||
|
||||
let affiliateURL = load.affiliateURLString.flatMap { URL(string: $0) }
|
||||
let deviceQuery = load.name.isEmpty
|
||||
@@ -1057,7 +1100,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
Item(
|
||||
id: Self.storageKey(for: load, itemID: "component"),
|
||||
logicalID: "component",
|
||||
title: load.name.isEmpty ? "Component" : load.name,
|
||||
title: load.name.isEmpty ? String(localized: "component.fallback.name", comment: "Fallback name for a component when no name is provided") : load.name,
|
||||
detail: powerDetail,
|
||||
iconSystemName: "bolt.fill",
|
||||
destination: affiliateURL.map { .affiliate($0) } ?? .amazonSearch(deviceQuery),
|
||||
@@ -1066,7 +1109,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
Item(
|
||||
id: Self.storageKey(for: load, itemID: "cable-red"),
|
||||
logicalID: "cable-red",
|
||||
title: "Power Cable (Red)",
|
||||
title: String(localized: "bom.item.cable.red", comment: "Title for the red power cable item"),
|
||||
detail: cableDetail,
|
||||
iconSystemName: "bolt.horizontal.circle",
|
||||
destination: .amazonSearch(redCableQuery),
|
||||
@@ -1075,7 +1118,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
Item(
|
||||
id: Self.storageKey(for: load, itemID: "cable-black"),
|
||||
logicalID: "cable-black",
|
||||
title: "Power Cable (Black)",
|
||||
title: String(localized: "bom.item.cable.black", comment: "Title for the black power cable item"),
|
||||
detail: cableDetail,
|
||||
iconSystemName: "bolt.horizontal.circle",
|
||||
destination: .amazonSearch(blackCableQuery),
|
||||
@@ -1084,7 +1127,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
Item(
|
||||
id: Self.storageKey(for: load, itemID: "fuse"),
|
||||
logicalID: "fuse",
|
||||
title: "Fuse & Holder",
|
||||
title: String(localized: "bom.item.fuse", comment: "Title for the fuse and holder item"),
|
||||
detail: fuseDetail,
|
||||
iconSystemName: "bolt.shield",
|
||||
destination: .amazonSearch(fuseQuery),
|
||||
@@ -1093,7 +1136,7 @@ private struct SystemBillOfMaterialsView: View {
|
||||
Item(
|
||||
id: Self.storageKey(for: load, itemID: "terminals"),
|
||||
logicalID: "terminals",
|
||||
title: "Cable Shoes / Terminals",
|
||||
title: String(localized: "bom.item.terminals", comment: "Title for the cable terminals item"),
|
||||
detail: cableShoesDetail,
|
||||
iconSystemName: "wrench.and.screwdriver",
|
||||
destination: .amazonSearch(terminalQuery),
|
||||
@@ -1171,7 +1214,10 @@ private struct SystemBillOfMaterialsView: View {
|
||||
}
|
||||
|
||||
private var footerMessage: String {
|
||||
return "Purchases through affiliate links may support VoltPlan."
|
||||
NSLocalizedString(
|
||||
"affiliate.disclaimer",
|
||||
comment: "Footer note reminding users that affiliate purchases may support the app"
|
||||
)
|
||||
}
|
||||
|
||||
private var dateFormatter: DateFormatter {
|
||||
|
||||
@@ -23,9 +23,9 @@ struct LoadEditorView: View {
|
||||
|
||||
var body: some View {
|
||||
ItemEditorView(
|
||||
title: "Edit Load",
|
||||
nameFieldLabel: "Load name",
|
||||
previewSubtitle: "Preview",
|
||||
title: String(localized: "editor.load.title", comment: "Title for the load editor"),
|
||||
nameFieldLabel: String(localized: "editor.load.name_field", comment: "Label for the load name text field"),
|
||||
previewSubtitle: String(localized: "editor.load.preview", comment: "Placeholder subtitle in the load editor preview"),
|
||||
icons: loadIcons,
|
||||
name: $loadName,
|
||||
iconName: $iconName,
|
||||
|
||||
32
Cable/OnboardingCarouselView.swift
Normal file
32
Cable/OnboardingCarouselView.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import SwiftUI
|
||||
|
||||
struct OnboardingCarouselView: View {
|
||||
let images: [String]
|
||||
let step: Int
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let width = geometry.size.width
|
||||
let height = geometry.size.height
|
||||
|
||||
ZStack {
|
||||
if images.isEmpty {
|
||||
Image(systemName: "photo")
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Color.secondary)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(Array(images.enumerated()), id: \.offset) { _, name in
|
||||
Image(name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: width, height: height)
|
||||
}
|
||||
}
|
||||
.offset(x: -CGFloat(step) * width)
|
||||
}
|
||||
}
|
||||
.clipped()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,17 +32,21 @@ struct SystemEditorView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let editorTitle = String(localized: "editor.system.title", comment: "Title for the system editor")
|
||||
let namePlaceholder = String(localized: "editor.system.name_field", comment: "Label for the system name text field")
|
||||
let locationPlaceholder = String(localized: "editor.system.location.optional", comment: "Placeholder text shown when no location is specified")
|
||||
|
||||
ItemEditorView(
|
||||
title: "Edit System",
|
||||
nameFieldLabel: "System name",
|
||||
previewSubtitle: tempLocation.isEmpty ? "Location (optional)" : tempLocation,
|
||||
title: editorTitle,
|
||||
nameFieldLabel: namePlaceholder,
|
||||
previewSubtitle: tempLocation.isEmpty ? locationPlaceholder : tempLocation,
|
||||
icons: systemIcons,
|
||||
name: $systemName,
|
||||
iconName: $iconName,
|
||||
colorName: $colorName,
|
||||
additionalFields: {
|
||||
AnyView(
|
||||
TextField("Location (optional)", text: $tempLocation)
|
||||
TextField(locationPlaceholder, text: $tempLocation)
|
||||
.autocapitalization(.words)
|
||||
.onChange(of: tempLocation) { _, newValue in
|
||||
location = newValue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SystemsOnboardingView: View {
|
||||
@State private var systemName: String = "My System"
|
||||
@State private var systemName: String = String(localized: "default.system.name", comment: "Default placeholder name for a system")
|
||||
@State private var carouselStep = 0
|
||||
@FocusState private var isFieldFocused: Bool
|
||||
let onCreate: (String) -> Void
|
||||
@@ -94,7 +94,7 @@ struct SystemsOnboardingView: View {
|
||||
}
|
||||
|
||||
private func resetState() {
|
||||
systemName = "My System"
|
||||
systemName = String(localized: "default.system.name", comment: "Default placeholder name for a system")
|
||||
carouselStep = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ enum UnitSystem: String, CaseIterable {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .metric:
|
||||
return "Metric (mm², m)"
|
||||
return String(localized: "units.metric.display", comment: "Display name for the metric unit system")
|
||||
case .imperial:
|
||||
return "Imperial (AWG, ft)"
|
||||
return String(localized: "units.imperial.display", comment: "Display name for the imperial unit system")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@ class UnitSystemSettings: ObservableObject {
|
||||
let savedSystem = UserDefaults.standard.string(forKey: "unitSystem") ?? UnitSystem.metric.rawValue
|
||||
self.unitSystem = UnitSystem(rawValue: savedSystem) ?? .metric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
Cable/de.lproj/Localizable.strings
Normal file
104
Cable/de.lproj/Localizable.strings
Normal file
@@ -0,0 +1,104 @@
|
||||
// Keys
|
||||
"affiliate.button.review_parts" = "Teile prüfen";
|
||||
"affiliate.description.with_link" = "Tippen oben zeigt eine vollständige Stückliste, bevor der Affiliate-Link geöffnet wird. Käufe können VoltPlan unterstützen.";
|
||||
"affiliate.description.without_link" = "Tippen oben zeigt eine vollständige Stückliste mit Einkaufssuchen, die dir bei der Beschaffung helfen.";
|
||||
"affiliate.disclaimer" = "Käufe über Affiliate-Links können VoltPlan unterstützen.";
|
||||
"bom.accessibility.mark.complete" = "Markiere %@ als erledigt";
|
||||
"bom.accessibility.mark.incomplete" = "Markiere %@ als unerledigt";
|
||||
"bom.fuse.detail" = "Inline-Halter und %dA-Sicherung";
|
||||
"bom.item.cable.black" = "Stromkabel (schwarz)";
|
||||
"bom.item.cable.red" = "Stromkabel (rot)";
|
||||
"bom.item.fuse" = "Sicherung & Halter";
|
||||
"bom.item.terminals" = "Kabelschuhe / Klemmen";
|
||||
"bom.navigation.title" = "Stückliste";
|
||||
"bom.navigation.title.system" = "Stückliste – %@";
|
||||
"bom.size.unknown" = "Größe offen";
|
||||
"bom.terminals.detail" = "Ring- oder Gabelkabelschuhe für %@-Leitungen";
|
||||
"component.fallback.name" = "Komponente";
|
||||
"default.load.library" = "Bibliothekslast";
|
||||
"default.load.name" = "Mein Verbraucher";
|
||||
"default.load.unnamed" = "Unbenannter Verbraucher";
|
||||
"default.load.new" = "Neuer Verbraucher";
|
||||
"default.system.name" = "Mein System";
|
||||
"default.system.new" = "Neues System";
|
||||
"editor.load.name_field" = "Name des Verbrauchers";
|
||||
"editor.load.preview" = "Vorschau";
|
||||
"editor.load.title" = "Verbraucher bearbeiten";
|
||||
"editor.system.location.optional" = "Standort (optional)";
|
||||
"editor.system.name_field" = "Name des Systems";
|
||||
"editor.system.title" = "System bearbeiten";
|
||||
"slider.button.ampere" = "Ampere";
|
||||
"slider.button.watt" = "Watt";
|
||||
"slider.current.title" = "Strom";
|
||||
"slider.length.title" = "Kabellänge (%@)";
|
||||
"slider.power.title" = "Leistung";
|
||||
"slider.voltage.title" = "Spannung";
|
||||
"system.list.no.components" = "Noch keine Komponenten";
|
||||
"units.imperial.display" = "Imperial (AWG, ft)";
|
||||
"units.metric.display" = "Metrisch (mm², m)";
|
||||
|
||||
// Direct strings
|
||||
"Systems" = "Systeme";
|
||||
"System" = "System";
|
||||
"System View" = "Systemansicht";
|
||||
"System Name" = "Systemname";
|
||||
"Create System" = "System erstellen";
|
||||
"Create your first system" = "Erstelle dein erstes System";
|
||||
"Give your setup a name so **Cable by VoltPlan** can organize loads, wiring, and recommendations in one place." = "Gib deinem Aufbau einen Namen, damit **Cable by VoltPlan** Verbraucher, Leitungen und Empfehlungen an einem Ort organisiert.";
|
||||
"Add your first component" = "Füge deine erste Komponente hinzu";
|
||||
"Bring your system to life with components and let **Cable by VoltPlan** handle cable and fuse recommendations." = "Erwecke dein System mit Komponenten zum Leben und überlasse **Cable by VoltPlan** die Kabel- und Sicherungsempfehlungen.";
|
||||
"Create Component" = "Komponente erstellen";
|
||||
"Browse Library" = "Bibliothek durchsuchen";
|
||||
"Browse" = "Durchsuchen";
|
||||
"Browse electrical components from VoltPlan" = "Elektrische Komponenten von VoltPlan durchstöbern";
|
||||
"Component Library" = "Komponentenbibliothek";
|
||||
"Details coming soon" = "Details folgen in Kürze";
|
||||
"Components" = "Komponenten";
|
||||
"FUSE" = "SICHERUNG";
|
||||
"WIRE" = "KABEL";
|
||||
"Current" = "Strom";
|
||||
"Power" = "Leistung";
|
||||
"Voltage" = "Spannung";
|
||||
"Length" = "Länge";
|
||||
"Length:" = "Länge:";
|
||||
"Wire Cross-Section:" = "Kabelquerschnitt:";
|
||||
"Current Units" = "Aktuelle Einheiten";
|
||||
"Unit System" = "Einheitensystem";
|
||||
"Units" = "Einheiten";
|
||||
"Settings" = "Einstellungen";
|
||||
"Close" = "Schließen";
|
||||
"Cancel" = "Abbrechen";
|
||||
"Save" = "Speichern";
|
||||
"Retry" = "Erneut versuchen";
|
||||
"Loading components" = "Komponenten werden geladen";
|
||||
"Unable to load components" = "Komponenten konnten nicht geladen werden";
|
||||
"No components available" = "Keine Komponenten verfügbar";
|
||||
"No matches" = "Keine Treffer";
|
||||
"Check back soon for new loads from VoltPlan." = "Schau bald wieder vorbei, um neue Verbraucher von VoltPlan zu finden.";
|
||||
"Try searching for a different name." = "Versuche, nach einem anderen Namen zu suchen.";
|
||||
"Search components" = "Komponenten suchen";
|
||||
"No loads saved in this system yet." = "In diesem System sind noch keine Verbraucher gespeichert.";
|
||||
"Coming soon - manage your electrical systems and panels here." = "Demnächst verfügbar – verwalte hier deine elektrischen Systeme und Verteilungen.";
|
||||
"Load Library" = "Verbraucher-bibliothek";
|
||||
"Safety Disclaimer" = "Sicherheitshinweis";
|
||||
"This application provides electrical calculations for educational and estimation purposes only." = "Diese Anwendung stellt elektrische Berechnungen nur zu Schulungs- und Schätzungszwecken bereit.";
|
||||
"Important:" = "Wichtig:";
|
||||
"• Always consult qualified electricians for actual installations" = "• Ziehe für tatsächliche Installationen stets qualifizierte Elektriker hinzu";
|
||||
"• Follow all local electrical codes and regulations" = "• Beachte alle örtlichen Vorschriften und Normen";
|
||||
"• Electrical work should only be performed by licensed professionals" = "• Elektroarbeiten sollten nur von zugelassenen Fachkräften ausgeführt werden";
|
||||
"• These calculations may not account for all environmental factors" = "• Diese Berechnungen berücksichtigen möglicherweise nicht alle Umgebungsfaktoren";
|
||||
"• The app developers assume no liability for electrical installations" = "• Die App-Entwickler übernehmen keine Haftung für elektrische Installationen";
|
||||
"Enter length in %@" = "Gib die Länge in %@ ein";
|
||||
"Enter voltage in volts (V)" = "Gib die Spannung in Volt (V) ein";
|
||||
"Enter current in amperes (A)" = "Gib den Strom in Ampere (A) ein";
|
||||
"Enter power in watts (W)" = "Gib die Leistung in Watt (W) ein";
|
||||
"Edit Length" = "Länge bearbeiten";
|
||||
"Edit Voltage" = "Spannung bearbeiten";
|
||||
"Edit Current" = "Strom bearbeiten";
|
||||
"Edit Power" = "Leistung bearbeiten";
|
||||
"Preview" = "Vorschau";
|
||||
"Details" = "Details";
|
||||
"Icon" = "Symbol";
|
||||
"Color" = "Farbe";
|
||||
"VoltPlan Library" = "VoltPlan-Bibliothek";
|
||||
"New Load" = "Neuer Verbraucher";
|
||||
22
Cable/de.lproj/Localizable.stringsdict
Normal file
22
Cable/de.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>system.list.component.summary</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@component_count@ • %@</string>
|
||||
<key>component_count</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d Komponente</string>
|
||||
<key>other</key>
|
||||
<string>%d Komponenten</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
104
Cable/es.lproj/Localizable.strings
Normal file
104
Cable/es.lproj/Localizable.strings
Normal file
@@ -0,0 +1,104 @@
|
||||
// Keys
|
||||
"affiliate.button.review_parts" = "Revisar componentes";
|
||||
"affiliate.description.with_link" = "Al tocar arriba se muestra una lista completa de materiales antes de abrir el enlace de afiliado. Las compras pueden ayudar a VoltPlan.";
|
||||
"affiliate.description.without_link" = "Al tocar arriba se muestra una lista completa de materiales con búsquedas de compra para ayudarte a conseguir piezas.";
|
||||
"affiliate.disclaimer" = "Las compras a través de enlaces de afiliados pueden ayudar a VoltPlan.";
|
||||
"bom.accessibility.mark.complete" = "Marcar %@ como completado";
|
||||
"bom.accessibility.mark.incomplete" = "Marcar %@ como pendiente";
|
||||
"bom.fuse.detail" = "Portafusibles en línea y fusible de %d A";
|
||||
"bom.item.cable.black" = "Cable de alimentación (negro)";
|
||||
"bom.item.cable.red" = "Cable de alimentación (rojo)";
|
||||
"bom.item.fuse" = "Fusible y portafusibles";
|
||||
"bom.item.terminals" = "Terminales / Zapatas";
|
||||
"bom.navigation.title" = "Lista de materiales";
|
||||
"bom.navigation.title.system" = "Lista de materiales – %@";
|
||||
"bom.size.unknown" = "Tamaño por definir";
|
||||
"bom.terminals.detail" = "Terminales de anillo o horquilla para cables de %@";
|
||||
"component.fallback.name" = "Componente";
|
||||
"default.load.library" = "Carga de biblioteca";
|
||||
"default.load.name" = "Mi carga";
|
||||
"default.load.unnamed" = "Carga sin nombre";
|
||||
"default.load.new" = "Carga nueva";
|
||||
"default.system.name" = "Mi sistema";
|
||||
"default.system.new" = "Sistema nuevo";
|
||||
"editor.load.name_field" = "Nombre de la carga";
|
||||
"editor.load.preview" = "Vista previa";
|
||||
"editor.load.title" = "Editar carga";
|
||||
"editor.system.location.optional" = "Ubicación (opcional)";
|
||||
"editor.system.name_field" = "Nombre del sistema";
|
||||
"editor.system.title" = "Editar sistema";
|
||||
"slider.button.ampere" = "Amperios";
|
||||
"slider.button.watt" = "Vatios";
|
||||
"slider.current.title" = "Corriente";
|
||||
"slider.length.title" = "Longitud del cable (%@)";
|
||||
"slider.power.title" = "Potencia";
|
||||
"slider.voltage.title" = "Voltaje";
|
||||
"system.list.no.components" = "Aún no hay componentes";
|
||||
"units.imperial.display" = "Imperial (AWG, ft)";
|
||||
"units.metric.display" = "Métrico (mm², m)";
|
||||
|
||||
// Direct strings
|
||||
"Systems" = "Sistemas";
|
||||
"System" = "Sistema";
|
||||
"System View" = "Vista del sistema";
|
||||
"System Name" = "Nombre del sistema";
|
||||
"Create System" = "Crear sistema";
|
||||
"Create your first system" = "Crea tu primer sistema";
|
||||
"Give your setup a name so **Cable by VoltPlan** can organize loads, wiring, and recommendations in one place." = "Ponle un nombre a tu instalación para que **Cable by VoltPlan** organice cargas, cableado y recomendaciones en un solo lugar.";
|
||||
"Add your first component" = "Añade tu primer componente";
|
||||
"Bring your system to life with components and let **Cable by VoltPlan** handle cable and fuse recommendations." = "Da vida a tu sistema con componentes y deja que **Cable by VoltPlan** se encargue de recomendar cables y fusibles.";
|
||||
"Create Component" = "Crear componente";
|
||||
"Browse Library" = "Explorar biblioteca";
|
||||
"Browse" = "Explorar";
|
||||
"Browse electrical components from VoltPlan" = "Explora los componentes eléctricos de VoltPlan";
|
||||
"Component Library" = "Biblioteca de componentes";
|
||||
"Details coming soon" = "Detalles próximamente";
|
||||
"Components" = "Componentes";
|
||||
"FUSE" = "FUSIBLE";
|
||||
"WIRE" = "CABLE";
|
||||
"Current" = "Corriente";
|
||||
"Power" = "Potencia";
|
||||
"Voltage" = "Voltaje";
|
||||
"Length" = "Longitud";
|
||||
"Length:" = "Longitud:";
|
||||
"Wire Cross-Section:" = "Sección del cable:";
|
||||
"Current Units" = "Unidades actuales";
|
||||
"Unit System" = "Sistema de unidades";
|
||||
"Units" = "Unidades";
|
||||
"Settings" = "Ajustes";
|
||||
"Close" = "Cerrar";
|
||||
"Cancel" = "Cancelar";
|
||||
"Save" = "Guardar";
|
||||
"Retry" = "Reintentar";
|
||||
"Loading components" = "Cargando componentes";
|
||||
"Unable to load components" = "No se pudieron cargar los componentes";
|
||||
"No components available" = "No hay componentes disponibles";
|
||||
"No matches" = "Sin coincidencias";
|
||||
"Check back soon for new loads from VoltPlan." = "Vuelve pronto para encontrar nuevas cargas de VoltPlan.";
|
||||
"Try searching for a different name." = "Prueba a buscar otro nombre.";
|
||||
"Search components" = "Buscar componentes";
|
||||
"No loads saved in this system yet." = "Todavía no hay cargas guardadas en este sistema.";
|
||||
"Coming soon - manage your electrical systems and panels here." = "Próximamente: gestiona aquí tus sistemas y paneles eléctricos.";
|
||||
"Load Library" = "Biblioteca de cargas";
|
||||
"Safety Disclaimer" = "Aviso de seguridad";
|
||||
"This application provides electrical calculations for educational and estimation purposes only." = "Esta aplicación proporciona cálculos eléctricos únicamente con fines educativos y de estimación.";
|
||||
"Important:" = "Importante:";
|
||||
"• Always consult qualified electricians for actual installations" = "• Consulta siempre a electricistas cualificados para las instalaciones reales";
|
||||
"• Follow all local electrical codes and regulations" = "• Cumple todas las normativas y códigos eléctricos locales";
|
||||
"• Electrical work should only be performed by licensed professionals" = "• Los trabajos eléctricos solo deben realizarlos profesionales autorizados";
|
||||
"• These calculations may not account for all environmental factors" = "• Estos cálculos pueden no tener en cuenta todos los factores ambientales";
|
||||
"• The app developers assume no liability for electrical installations" = "• Los desarrolladores de la app no asumen responsabilidad por las instalaciones eléctricas";
|
||||
"Enter length in %@" = "Introduce la longitud en %@";
|
||||
"Enter voltage in volts (V)" = "Introduce el voltaje en voltios (V)";
|
||||
"Enter current in amperes (A)" = "Introduce la corriente en amperios (A)";
|
||||
"Enter power in watts (W)" = "Introduce la potencia en vatios (W)";
|
||||
"Edit Length" = "Editar longitud";
|
||||
"Edit Voltage" = "Editar voltaje";
|
||||
"Edit Current" = "Editar corriente";
|
||||
"Edit Power" = "Editar potencia";
|
||||
"Preview" = "Vista previa";
|
||||
"Details" = "Detalles";
|
||||
"Icon" = "Icono";
|
||||
"Color" = "Color";
|
||||
"VoltPlan Library" = "Biblioteca de VoltPlan";
|
||||
"New Load" = "Carga nueva";
|
||||
22
Cable/es.lproj/Localizable.stringsdict
Normal file
22
Cable/es.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>system.list.component.summary</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@component_count@ • %@</string>
|
||||
<key>component_count</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d componente</string>
|
||||
<key>other</key>
|
||||
<string>%d componentes</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user