diff --git a/PasswordBox.xcodeproj/project.pbxproj b/PasswordBox.xcodeproj/project.pbxproj index ed0e0a9..f49640c 100644 --- a/PasswordBox.xcodeproj/project.pbxproj +++ b/PasswordBox.xcodeproj/project.pbxproj @@ -19,6 +19,9 @@ 3F13F9C82C1DED720068908E /* EditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F13F9C72C1DED720068908E /* EditView.swift */; }; 3F13F9CB2C1DEE020068908E /* PasswordFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F13F9CA2C1DEE020068908E /* PasswordFormView.swift */; }; 3F1DFECF2C4B552E00BAA193 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1DFECE2C4B552E00BAA193 /* SafariView.swift */; }; + 3F1DFEDD2C4CA01300BAA193 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3F1DFEDC2C4CA01300BAA193 /* Localizable.xcstrings */; }; + 3F1DFEE12C4CB06700BAA193 /* CommonButtonStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1DFEE02C4CB06700BAA193 /* CommonButtonStrings.swift */; }; + 3F1DFEE32C4CB5F300BAA193 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3F1DFEE22C4CB5F300BAA193 /* InfoPlist.xcstrings */; }; 3F3DE2F82C1DE2500089CB5A /* InfomationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3DE2F72C1DE2500089CB5A /* InfomationView.swift */; }; 3F3DE2FA2C1DE2710089CB5A /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3DE2F92C1DE2710089CB5A /* AddView.swift */; }; 3F6AA4B82C2FDF22008EC918 /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6AA4B72C2FDF22008EC918 /* PasswordField.swift */; }; @@ -68,6 +71,9 @@ 3F13F9C72C1DED720068908E /* EditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditView.swift; sourceTree = ""; }; 3F13F9CA2C1DEE020068908E /* PasswordFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordFormView.swift; sourceTree = ""; }; 3F1DFECE2C4B552E00BAA193 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; + 3F1DFEDC2C4CA01300BAA193 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 3F1DFEE02C4CB06700BAA193 /* CommonButtonStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonButtonStrings.swift; sourceTree = ""; }; + 3F1DFEE22C4CB5F300BAA193 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 3F3DE2F72C1DE2500089CB5A /* InfomationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfomationView.swift; sourceTree = ""; }; 3F3DE2F92C1DE2710089CB5A /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; 3F6AA4B72C2FDF22008EC918 /* PasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordField.swift; sourceTree = ""; }; @@ -291,6 +297,9 @@ 3F99FC502C390D65001ED4B2 /* IconStyles.swift */, 3F026F432C3A789500ED696C /* URLs.swift */, 3FEE0F152C3C2EDE0057EE2F /* AppTips.swift */, + 3F1DFEE02C4CB06700BAA193 /* CommonButtonStrings.swift */, + 3F1DFEDC2C4CA01300BAA193 /* Localizable.xcstrings */, + 3F1DFEE22C4CB5F300BAA193 /* InfoPlist.xcstrings */, ); path = Resources; sourceTree = ""; @@ -380,12 +389,14 @@ }; buildConfigurationList = 3F8C85922C189D4A0032277E /* Build configuration list for PBXProject "PasswordBox" */; compatibilityVersion = "Xcode 14.0"; - developmentRegion = ja; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ja, + "zh-Hans", + fr, ); mainGroup = 3F8C858E2C189D4A0032277E; packageReferences = ( @@ -410,6 +421,8 @@ files = ( 3F8C85A22C189D4B0032277E /* Preview Assets.xcassets in Resources */, 3F8C859F2C189D4B0032277E /* Assets.xcassets in Resources */, + 3F1DFEE32C4CB5F300BAA193 /* InfoPlist.xcstrings in Resources */, + 3F1DFEDD2C4CA01300BAA193 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -448,6 +461,7 @@ 3F026F292C39A2E400ED696C /* EditViewModel.swift in Sources */, 3F6AA4BE2C3130FC008EC918 /* CreatePassword.swift in Sources */, 3F6AA4B82C2FDF22008EC918 /* PasswordField.swift in Sources */, + 3F1DFEE12C4CB06700BAA193 /* CommonButtonStrings.swift in Sources */, 3F99FC4F2C390873001ED4B2 /* Images.swift in Sources */, 3F99FC592C39258B001ED4B2 /* Entry.swift in Sources */, 3F13F9C82C1DED720068908E /* EditView.swift in Sources */, @@ -625,7 +639,7 @@ DEVELOPMENT_TEAM = B58T472MM3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSFaceIDUsageDescription = "FaceID を使用してアプリへのアクセスを保護し、安全な認証を提供します"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Use FaceID to secure access to apps and provide secure authentication"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -661,7 +675,7 @@ DEVELOPMENT_TEAM = B58T472MM3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSFaceIDUsageDescription = "FaceID を使用してアプリへのアクセスを保護し、安全な認証を提供します"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Use FaceID to secure access to apps and provide secure authentication"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/PasswordBox/ContentView.swift b/PasswordBox/ContentView.swift index b246a44..a6c0b26 100644 --- a/PasswordBox/ContentView.swift +++ b/PasswordBox/ContentView.swift @@ -18,15 +18,15 @@ struct ContentView: View { .cornerRadius(4) .padding(.horizontal, 16) - Text("ログイン認証が必要です") + Text("Login authentication is required") .font(.title) - Text("アプリを使用するには端末の認証が必要です。\n 続けるには**再認証**をしてください") + Text("You must authenticate your device in order to use the application. \n Please **re-authenticate** to continue") .multilineTextAlignment(.center) Button(action: { authenticate() }, label: { - Text("再認証する") + Text("Re-authenticate") .padding() .foregroundColor(.white) .background(.blue) @@ -48,7 +48,7 @@ struct ContentView: View { var error: NSError? if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) { - let description = "認証が必要です" + let description = String(localized: "Authentication required") context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: description) { success, authenticationError in DispatchQueue.main.async { diff --git a/PasswordBox/Resources/AppTips.swift b/PasswordBox/Resources/AppTips.swift index 28d20df..aa34e29 100644 --- a/PasswordBox/Resources/AppTips.swift +++ b/PasswordBox/Resources/AppTips.swift @@ -3,10 +3,10 @@ import TipKit struct AppTips { struct ChangeIcon: Tip { var title: Text { - Text("アイコンとタイトルを変更") + Text("Change icon and title") } var message: Text? { - Text("アイコンとタイトルはこちらから編集することができます。") + Text("Icons and titles can be edited here.") } } diff --git a/PasswordBox/Resources/CommonButtonStrings.swift b/PasswordBox/Resources/CommonButtonStrings.swift new file mode 100644 index 0000000..cbc0475 --- /dev/null +++ b/PasswordBox/Resources/CommonButtonStrings.swift @@ -0,0 +1,6 @@ +import Foundation + +struct CommonButtonStrings { + static let done: String = .init(localized: "Done") + static let cancel: String = .init(localized: "Cancel") +} diff --git a/PasswordBox/Resources/InfoPlist.xcstrings b/PasswordBox/Resources/InfoPlist.xcstrings new file mode 100644 index 0000000..50f2bc8 --- /dev/null +++ b/PasswordBox/Resources/InfoPlist.xcstrings @@ -0,0 +1,42 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "CFBundleName" : { + "comment" : "Bundle name", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "PasswordBox" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "PasswordBox" + } + } + } + }, + "NSFaceIDUsageDescription" : { + "comment" : "Privacy - Face ID Usage Description", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Use FaceID to secure access to apps and provide secure authentication" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "FaceIDを使用してアプリへのアクセスを保護し、安全な認証を提供します。" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/PasswordBox/Resources/Localizable.xcstrings b/PasswordBox/Resources/Localizable.xcstrings new file mode 100644 index 0000000..c4dfcbb --- /dev/null +++ b/PasswordBox/Resources/Localizable.xcstrings @@ -0,0 +1,296 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Add" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加" + } + } + } + }, + "alphabet" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アルファベット" + } + } + } + }, + "App Infomation" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アプリ情報" + } + } + } + }, + "Authentication required" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認証が必要です" + } + } + } + }, + "Automatic Password Update" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パスワードを自動更新" + } + } + } + }, + "Cancel" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "キャンセル" + } + } + } + }, + "Change icon and title" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アイコンとタイトルを変更" + } + } + } + }, + "Contact Us" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "お問い合わせ" + } + } + } + }, + "Done" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "完了" + } + } + } + }, + "Edit" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "編集" + } + } + } + }, + "Edit password manually" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パスワードを手動で編集" + } + } + } + }, + "Icons and titles can be edited here." : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アイコンとタイトルはこちらから編集することができます。" + } + } + } + }, + "Infomation" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "情報" + } + } + } + }, + "LICENCE" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ライセンス" + } + } + } + }, + "List" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リスト" + } + } + } + }, + "List Name" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リスト名" + } + } + } + }, + "Login authentication is required" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログイン認証が必要です" + } + } + } + }, + "number of characters: %lld" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "文字数: %lld" + } + } + } + }, + "OK" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + } + } + }, + "PasswordField" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "PasswordField" + } + } + } + }, + "Privacy Policy" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プライバシーポリシー" + } + } + } + }, + "Re-authenticate" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再認証する" + } + } + } + }, + "Select Icon" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アイコン選択" + } + } + } + }, + "symbol" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "記号" + } + } + } + }, + "Text Field" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "テキストフィールド" + } + } + } + }, + "unkown" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不明" + } + } + } + }, + "Version" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "バージョン" + } + } + } + }, + "WebSite" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webサイト" + } + } + } + }, + "You must authenticate your device in order to use the application. \n Please **re-authenticate** to continue" : { + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アプリを使用するには端末の認証が必要です。続けるには**再認証**をしてください" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/PasswordBox/UI/Common/PasswordField.swift b/PasswordBox/UI/Common/PasswordField.swift index 3be984c..d363bb0 100644 --- a/PasswordBox/UI/Common/PasswordField.swift +++ b/PasswordBox/UI/Common/PasswordField.swift @@ -22,8 +22,8 @@ struct PasswordField: View { .privacySensitive(isShowSecure) .redacted(reason: .privacy) } - .alert("パスワードを手動で編集", isPresented: $isPresented) { - TextField("テキストフィールド", text: $text) + .alert("Edit password manually", isPresented: $isPresented) { + TextField("Text Field", text: $text) Button { print("OK") diff --git a/PasswordBox/UI/Common/PasswordFormView.swift b/PasswordBox/UI/Common/PasswordFormView.swift index 8f1fe19..46bf764 100644 --- a/PasswordBox/UI/Common/PasswordFormView.swift +++ b/PasswordBox/UI/Common/PasswordFormView.swift @@ -39,7 +39,6 @@ struct PasswordFormView: View { .padding() Button(action: { - print("更新します") passwordString = CreatePassword.createPassword( length: passwordLength, isAlphabet, @@ -47,7 +46,7 @@ struct PasswordFormView: View { ) }, label: { - Text("パスワードを自動更新") + Text("Automatic Password Update") .padding() .foregroundColor(.white) .background(.blue) @@ -57,20 +56,20 @@ struct PasswordFormView: View { Section { VStack(spacing: 30) { Stepper(value: $passwordLength, in: 1...100) { - Text("文字数: \(passwordLength)") + Text("number of characters: \(passwordLength)") } Toggle(isOn: $isAlphabet, label: { - Text("アルファベット") + Text("alphabet") }) Toggle(isOn: $isSymbol, label: { - Text("記号") + Text("symbol") }) // TODO: 今後実装 // Toggle(isOn: $isNotice, label: { -// Text("通知") +// Text("notice") // }) } .padding(.horizontal, 20) diff --git a/PasswordBox/UI/Info/InfomationView.swift b/PasswordBox/UI/Info/InfomationView.swift index 28fceab..27fd4e9 100644 --- a/PasswordBox/UI/Info/InfomationView.swift +++ b/PasswordBox/UI/Info/InfomationView.swift @@ -2,35 +2,29 @@ import SwiftUI struct InfomationView: View { private let viewModel = InfomationViewModel() - private let contactUsTitle = "お問い合わせ" - private let privacyPolicy = "プライバシーポリシー" - private let releaseNote = "リリースノート" - private let WebSite = "Webサイト" - private let licenceTitle = "LICENCE" var body: some View { List { - Section("情報") { - Link(privacyPolicy, destination: URL.InfomationView.privacyPolicy) + Section("Infomation") { + Link("Privacy Policy", destination: URL.InfomationView.privacyPolicy) .openURLInSafariView() - Link(contactUsTitle, destination: URL.InfomationView.contactURL) + Link("Contact Us", destination: URL.InfomationView.contactURL) .openURLInSafariView() - Link(WebSite, destination: URL.InfomationView.websiteURL) + Link("WebSite", destination: URL.InfomationView.websiteURL) .openURLInSafariView() - NavigationLink(licenceTitle) { + NavigationLink("LICENCE") { LicenseList() - .navigationTitle(licenceTitle) } } Section { - LabeledContent("バージョン", value: viewModel.versionString) + LabeledContent("Version", value: viewModel.versionString) } } - .navigationTitle("アプリ情報") + .navigationTitle("App Infomation") } } diff --git a/PasswordBox/UI/Info/InfomationViewModel.swift b/PasswordBox/UI/Info/InfomationViewModel.swift index 5e8a16d..d7d0f2e 100644 --- a/PasswordBox/UI/Info/InfomationViewModel.swift +++ b/PasswordBox/UI/Info/InfomationViewModel.swift @@ -3,7 +3,7 @@ import SwiftUI struct InfomationViewModel { var versionString: String { let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String - return version ?? "不明" + return version ?? String(localized: "unkown") } } diff --git a/PasswordBox/UI/List/Add/AddView.swift b/PasswordBox/UI/List/Add/AddView.swift index 330afce..2382311 100644 --- a/PasswordBox/UI/List/Add/AddView.swift +++ b/PasswordBox/UI/List/Add/AddView.swift @@ -14,16 +14,16 @@ struct AddView: View { var body: some View { NavigationStack { PasswordFormView(title: $title, iconString: $iconString, passwordString: $passwordString, passwordLength: $passwordLength) - .navigationTitle("追加") + .navigationTitle("Add") .toolbar { ToolbarItem(placement: .topBarLeading) { - Button("キャンセル") { + Button(CommonButtonStrings.cancel) { dismiss() } } ToolbarItem(placement: .topBarTrailing) { - Button("完了") { + Button(CommonButtonStrings.done) { viewModel.add(context, title, iconString, passwordString) dismiss() } diff --git a/PasswordBox/UI/List/CellView.swift b/PasswordBox/UI/List/CellView.swift index eff2091..27ab72b 100644 --- a/PasswordBox/UI/List/CellView.swift +++ b/PasswordBox/UI/List/CellView.swift @@ -31,10 +31,10 @@ struct CellView: View { let date = entry.date let formatter = DateFormatter() formatter.dateStyle = .medium - formatter.timeStyle = .medium + formatter.timeStyle = .none formatter.timeZone = .current formatter.locale = .current - formatter.dateFormat = "yyyy年MM月dd日" + formatter.doesRelativeDateFormatting = true return formatter.string(from: date) } } diff --git a/PasswordBox/UI/List/Edit/EditView.swift b/PasswordBox/UI/List/Edit/EditView.swift index 895f015..2688a95 100644 --- a/PasswordBox/UI/List/Edit/EditView.swift +++ b/PasswordBox/UI/List/Edit/EditView.swift @@ -11,10 +11,10 @@ struct EditView: View { var body: some View { NavigationStack { PasswordFormView(title: $entry.title, iconString: $entry.iconString, passwordString: $passwordString, passwordLength: $passwordLength) - .navigationTitle("編集") + .navigationTitle("Edit") .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button("完了") { + Button(CommonButtonStrings.done) { viewModel.save(context, key: entry.id, passwordString) dismiss() } @@ -23,7 +23,6 @@ struct EditView: View { } .onAppear { load(key: entry.id) - print("entryid: \(entry.id)") } } diff --git a/PasswordBox/UI/List/ListView.swift b/PasswordBox/UI/List/ListView.swift index 598944a..7dbb533 100644 --- a/PasswordBox/UI/List/ListView.swift +++ b/PasswordBox/UI/List/ListView.swift @@ -25,7 +25,7 @@ struct ListView: View { } }) } - .navigationTitle("リスト") + .navigationTitle("List") .toolbar { ToolbarItem(placement: .topBarLeading) { Button { diff --git a/PasswordBox/UI/List/SettingIcon/IconView.swift b/PasswordBox/UI/List/SettingIcon/IconView.swift index 266c396..3a88795 100644 --- a/PasswordBox/UI/List/SettingIcon/IconView.swift +++ b/PasswordBox/UI/List/SettingIcon/IconView.swift @@ -18,7 +18,7 @@ struct IconView: View { .padding(.horizontal, 16) TextField(text: $title, axis: .vertical) { - Text("リスト名") + Text("List Name") } .font(.title) .textFieldStyle(.roundedBorder) @@ -27,7 +27,7 @@ struct IconView: View { } } - // TODO: 検討中 + // TODO: under consideration // Section { // ScrollView(.horizontal, showsIndicators: false) { // HStack { diff --git a/PasswordBox/UI/List/SettingIcon/SettingIconView.swift b/PasswordBox/UI/List/SettingIcon/SettingIconView.swift index 549a523..b7e4c54 100644 --- a/PasswordBox/UI/List/SettingIcon/SettingIconView.swift +++ b/PasswordBox/UI/List/SettingIcon/SettingIconView.swift @@ -7,16 +7,16 @@ struct SettingIconView: View { var body: some View { NavigationStack { IconView(title: $title, iconString: $iconString) - .navigationTitle("アイコンを選択") + .navigationTitle("Select Icon") .toolbar { ToolbarItem(placement: .topBarLeading) { - Button("キャンセル") { + Button(CommonButtonStrings.cancel) { dismiss() } } ToolbarItem(placement: .topBarTrailing) { - Button("完了") { + Button(CommonButtonStrings.done) { dismiss() } }