Skip to content

Commit 562ba6f

Browse files
authored
Feature/Add hover modifier to see the full directory path and click to copy it (#217)
1 parent 7409b23 commit 562ba6f

File tree

4 files changed

+127
-64
lines changed

4 files changed

+127
-64
lines changed

Lingua-App/Lingua/Lingua/Resources/Localization/Lingua.swift

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,132 +4,134 @@
44

55
import Foundation
66

7-
enum Lingua {
8-
enum App {
7+
public enum Lingua {
8+
public enum App {
99
/// About Lingua
10-
static let about = tr("App", "about")
10+
public static let about = tr("App", "about")
1111
/// Copyright
12-
static let copyright = tr("App", "copyright")
12+
public static let copyright = tr("App", "copyright")
1313
/// © 2023 Povio Inc.
14-
static let copyrightYear = tr("App", "copyright_year")
14+
public static let copyrightYear = tr("App", "copyright_year")
1515
/// A unified localization management tool for iOS & Android
16-
static let description = tr("App", "description")
16+
public static let description = tr("App", "description")
1717
/// Lingua Settings...
18-
static let settings = tr("App", "settings")
18+
public static let settings = tr("App", "settings")
1919
}
2020

21-
enum General {
21+
public enum General {
2222
/// Choose
23-
static let choose = tr("General", "choose")
23+
public static let choose = tr("General", "choose")
2424
/// Delete
25-
static let delete = tr("General", "delete")
25+
public static let delete = tr("General", "delete")
2626
/// Duplicate
27-
static let duplicate = tr("General", "duplicate")
27+
public static let duplicate = tr("General", "duplicate")
2828
/// Error
29-
static let error = tr("General", "error")
29+
public static let error = tr("General", "error")
3030
/// Save
31-
static let save = tr("General", "save")
31+
public static let save = tr("General", "save")
3232
/// Search
33-
static let search = tr("General", "search")
33+
public static let search = tr("General", "search")
3434
/// Success
35-
static let success = tr("General", "success")
35+
public static let success = tr("General", "success")
3636
/// this
37-
static let this = tr("General", "this")
37+
public static let this = tr("General", "this")
3838
}
3939

40-
enum ProjectForm {
40+
public enum ProjectForm {
4141
/// Here are the steps to enable the Google Sheets API and create an API key:\n\n* Go to the https://console.cloud.google.com/.\n* If you haven't already, create a new project or select an existing one.\n* In the left sidebar, click on "APIs & Services"\n* Click on "+ ENABLE APIS AND SERVICES" at the top of the page.\n* In the search bar, type "Google Sheets API" and select it from the list.\n* Click on "ENABLE" to enable the Google Sheets API for your project.\n* After the API is enabled, go back to the "APIs & Services" > "Credendtials" page.\n* Click on "CREATE CREDENTIALS" at the top of the page.\n* In the dropdown, select "API key"\n* Wait a bit until the key is generated and an information modal with the message API key created will be shown.
42-
static let apiKeyHelp = tr("ProjectForm", "api_key_help")
42+
public static let apiKeyHelp = tr("ProjectForm", "api_key_help")
4343
/// Configuration
44-
static let configurationSection = tr("ProjectForm", "configuration_section")
44+
public static let configurationSection = tr("ProjectForm", "configuration_section")
45+
/// Copied to clipboard!
46+
public static let copiedToClipboard = tr("ProjectForm", "copied_to_clipboard")
4547
/// Add section
46-
static let filteringAddSectionButtonTitle = tr("ProjectForm", "filtering_add_section_button_title")
48+
public static let filteringAddSectionButtonTitle = tr("ProjectForm", "filtering_add_section_button_title")
4749
/// Add the sections that you want to include into the project, otherwise if it is disabled all the sections will be included
48-
static let filteringSectionDescription = tr("ProjectForm", "filtering_section_description")
50+
public static let filteringSectionDescription = tr("ProjectForm", "filtering_section_description")
4951
/// Enter a section
50-
static let filteringSectionTextfieldPlaceholder = tr("ProjectForm", "filtering_section_textfield_placeholder")
52+
public static let filteringSectionTextfieldPlaceholder = tr("ProjectForm", "filtering_section_textfield_placeholder")
5153
/// Enable sections filtering
52-
static let filteringSectionTitle = tr("ProjectForm", "filtering_section_title")
54+
public static let filteringSectionTitle = tr("ProjectForm", "filtering_section_title")
5355
/// Info
54-
static let infoHeader = tr("ProjectForm", "info_header")
56+
public static let infoHeader = tr("ProjectForm", "info_header")
5557
/// API Key *
56-
static let inputApiKey = tr("ProjectForm", "input_api_key")
58+
public static let inputApiKey = tr("ProjectForm", "input_api_key")
5759
/// Choose Directory
58-
static let inputDirectoryButton = tr("ProjectForm", "input_directory_button")
60+
public static let inputDirectoryButton = tr("ProjectForm", "input_directory_button")
5961
/// Output directory *
60-
static let inputDirectoryOutput = tr("ProjectForm", "input_directory_output")
62+
public static let inputDirectoryOutput = tr("ProjectForm", "input_directory_output")
6163
/// Name *
62-
static let inputProjectName = tr("ProjectForm", "input_project_name")
64+
public static let inputProjectName = tr("ProjectForm", "input_project_name")
6365
/// Sheet ID *
64-
static let inputSheetId = tr("ProjectForm", "input_sheet_id")
66+
public static let inputSheetId = tr("ProjectForm", "input_sheet_id")
6567
/// After you "Localize", you have to Add files to "%@"... in Xcode, if they are not added already.\n\nNOTE: If you are using Xcode 16 and have structured your project using 'Folders' instead of 'Groups', this step is not necessary.
66-
static func iosLocalizationInfoMessage(_ param1: String) -> String {
68+
public static func iosLocalizationInfoMessage(_ param1: String) -> String {
6769
return tr("ProjectForm", "ios_localization_info_message", param1)
6870
}
6971
/// Last localized: %@
70-
static func lastLocalizedSubtitle(_ param1: String) -> String {
72+
public static func lastLocalizedSubtitle(_ param1: String) -> String {
7173
return tr("ProjectForm", "last_localized_subtitle", param1)
7274
}
7375
/// Lingua.swift Directory *
74-
static let linguaSwiftOutputDirectory = tr("ProjectForm", "lingua_swift_output_directory")
76+
public static let linguaSwiftOutputDirectory = tr("ProjectForm", "lingua_swift_output_directory")
7577
/// This should be the directory where you want to store the generated Lingua.swift file
76-
static let linguaSwiftOutputDirectoryHelp = tr("ProjectForm", "lingua_swift_output_directory_help")
78+
public static let linguaSwiftOutputDirectoryHelp = tr("ProjectForm", "lingua_swift_output_directory_help")
7779
/// Localize
78-
static let localizeButton = tr("ProjectForm", "localize_button")
80+
public static let localizeButton = tr("ProjectForm", "localize_button")
7981
/// The .lproj directory should be the directory where .strings files are saved.\nIt serves as base language directory from where the Lingua.swift file will be created
80-
static let lprojDirectoryHelp = tr("ProjectForm", "lproj_directory_help")
82+
public static let lprojDirectoryHelp = tr("ProjectForm", "lproj_directory_help")
8183
/// The output directory property should be the path where you want the tool to create localization files.\n\n* For iOS it can be any directory on your project. After you run the command, for the first time, \n you have to Add files to 'YourProject' in Xcode.\n\n* For Android, since the translation are placed in a specific project directory,\n the output directory it should look something like this: path/YourProject/app/src/main/res
82-
static let outputDirectoryHelp = tr("ProjectForm", "output_directory_help")
84+
public static let outputDirectoryHelp = tr("ProjectForm", "output_directory_help")
8385
/// Platform *
84-
static let platformPickerTitle = tr("ProjectForm", "platform_picker_title")
86+
public static let platformPickerTitle = tr("ProjectForm", "platform_picker_title")
8587
/// * Make a copy of the [Sheet Template](https://docs.google.com/spreadsheets/d/1Cnqy4gZqh9pGcTF_0jb8QGOnysejZ8dVfSj8dgX4kzM) from menu "File > Make a copy"\n* Ensure that the Google Sheet you're trying to access has its sharing settings configured to allow access to anyone with the link.\n You can do this by clicking on "Share" in the upper right corner of the Google Sheet and selecting "Anyone with the link."\n* The sheet id can easly be accessed from the url after you have create a copy of the document tamplate.\n\nExample:\n\nhttps://docs.google.com/spreadsheets/d/ 1GpaPpO4JMleZPd8paSW4qPBQxjImm2xD8yJhvZOP-8w
86-
static let sheetIdHelp = tr("ProjectForm", "sheet_id_help")
88+
public static let sheetIdHelp = tr("ProjectForm", "sheet_id_help")
8789
/// .lproj Directory *
88-
static let stringsDirectory = tr("ProjectForm", "strings_directory")
90+
public static let stringsDirectory = tr("ProjectForm", "strings_directory")
8991
/// Since iOS does not have a built in feature to access the localization safely, we have made this possible using Lingua tool. Below you have to provide the path where the Swift file you want to be created. With that the tool will create Lingua.swift with an enumeration to easily access localizations in your app.
90-
static let swiftCodeDescription = tr("ProjectForm", "swift_code_description")
92+
public static let swiftCodeDescription = tr("ProjectForm", "swift_code_description")
9193
/// iOS Swift Code Settings
92-
static let swiftCodeSection = tr("ProjectForm", "swift_code_section")
94+
public static let swiftCodeSection = tr("ProjectForm", "swift_code_section")
9395
/// Generate Swift Code
94-
static let swiftCodeToggleTitle = tr("ProjectForm", "swift_code_toggle_title")
96+
public static let swiftCodeToggleTitle = tr("ProjectForm", "swift_code_toggle_title")
9597
}
9698

97-
enum ProjectMenu {
99+
public enum ProjectMenu {
98100
/// Delete
99-
static let delete = tr("ProjectMenu", "delete")
101+
public static let delete = tr("ProjectMenu", "delete")
100102
/// Duplicate
101-
static let duplicate = tr("ProjectMenu", "duplicate")
103+
public static let duplicate = tr("ProjectMenu", "duplicate")
102104
/// Localize
103-
static let localize = tr("ProjectMenu", "localize")
105+
public static let localize = tr("ProjectMenu", "localize")
104106
/// New
105-
static let new = tr("ProjectMenu", "new")
107+
public static let new = tr("ProjectMenu", "new")
106108
/// Project
107-
static let title = tr("ProjectMenu", "title")
109+
public static let title = tr("ProjectMenu", "title")
108110
}
109111

110-
enum Projects {
112+
public enum Projects {
111113
/// %@ copy
112-
static func copyProject(_ param1: String) -> String {
114+
public static func copyProject(_ param1: String) -> String {
113115
return tr("Projects", "copy_project", param1)
114116
}
115117
/// Are you sure you want to delete "%@" project?
116-
static func deleteAlertMessage(_ param1: String) -> String {
118+
public static func deleteAlertMessage(_ param1: String) -> String {
117119
return tr("Projects", "delete_alert_message", param1)
118120
}
119121
/// Confirmation
120-
static let deleteAlertTitle = tr("Projects", "delete_alert_title")
122+
public static let deleteAlertTitle = tr("Projects", "delete_alert_title")
121123
/// Projects
122-
static let listSectionHeader = tr("Projects", "list_section_header")
124+
public static let listSectionHeader = tr("Projects", "list_section_header")
123125
/// "%@" has been successfully localized.
124-
static func localizedMessage(_ param1: String) -> String {
126+
public static func localizedMessage(_ param1: String) -> String {
125127
return tr("Projects", "localized_message", param1)
126128
}
127129
/// Localizing...
128-
static let localizing = tr("Projects", "localizing")
130+
public static let localizing = tr("Projects", "localizing")
129131
/// New project
130-
static let newProject = tr("Projects", "new_project")
132+
public static let newProject = tr("Projects", "new_project")
131133
/// Select a project or add a new one.
132-
static let placeholder = tr("Projects", "placeholder")
134+
public static let placeholder = tr("Projects", "placeholder")
133135
}
134136

135137
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {

Lingua-App/Lingua/Lingua/Resources/Localization/en.lproj/ProjectForm.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ It serves as base language directory from where the Lingua.swift file will be cr
5353
"filtering_section_description" = "Add the sections that you want to include into the project, otherwise if it is disabled all the sections will be included";
5454
"filtering_section_textfield_placeholder" = "Enter a section";
5555
"filtering_add_section_button_title" = "Add section";
56+
"copied_to_clipboard" = "Copied to clipboard!";

Lingua-App/Lingua/Lingua/Scenes/ProjectFormView/ProjectFormView.swift

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ struct ProjectFormView: View {
1818
@State private var outputPathValid = false
1919
@State private var stringsDirectoryValid = false
2020
@State private var outputSwiftCodeFileDirectoryValid = false
21-
21+
@State private var copied = false
22+
2223
var onSave: ((Project) -> Void)? = nil
2324
var onDelete: ((Project) -> Void)? = nil
2425
var onLocalize: ((Project) -> Void)? = nil
@@ -52,6 +53,18 @@ struct ProjectFormView: View {
5253
deleteButton(for: viewModel.project).padding()
5354
}
5455
.padding()
56+
.overlay {
57+
Text(Lingua.ProjectForm.copiedToClipboard)
58+
.padding(8)
59+
.background(
60+
Color.black
61+
.opacity(0.4)
62+
)
63+
.clipShape(RoundedRectangle(cornerRadius: 6))
64+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
65+
.padding(.top, 6)
66+
.shouldAddView(copied)
67+
}
5568
}
5669
}
5770

@@ -65,7 +78,7 @@ private extension ProjectFormView {
6578
Text(type.title)
6679
.tag(type)
6780
}
68-
}.padding(.leading, 8)
81+
}
6982

7083
ValidatingTextField(
7184
title: Lingua.ProjectForm.inputProjectName,
@@ -104,7 +117,8 @@ private extension ProjectFormView {
104117
bookmarkDataKey: viewModel.project.bookmarkDataForDirectoryPath,
105118
directoryPath: $viewModel.project.directoryPath,
106119
isValid: $outputPathValid,
107-
onDirectorySelected: updateDirectoryPaths
120+
onDirectorySelected: updateDirectoryPaths,
121+
onDirectoryCopied: showCopiedMessage
108122
)
109123
}) {
110124
Text(.init(Lingua.ProjectForm.outputDirectoryHelp))
@@ -135,7 +149,8 @@ private extension ProjectFormView {
135149
title: Lingua.ProjectForm.stringsDirectory,
136150
bookmarkDataKey: viewModel.project.bookmarkDataForStringsDirectory,
137151
directoryPath: $viewModel.project.swiftCode.stringsDirectory,
138-
isValid: $stringsDirectoryValid
152+
isValid: $stringsDirectoryValid,
153+
onDirectoryCopied: showCopiedMessage
139154
)
140155
}) {
141156
Text(.init(Lingua.ProjectForm.lprojDirectoryHelp))
@@ -147,7 +162,8 @@ private extension ProjectFormView {
147162
title: Lingua.ProjectForm.linguaSwiftOutputDirectory,
148163
bookmarkDataKey: viewModel.project.bookmarkDataForOutputSwiftCodeFileDirectory ,
149164
directoryPath: $viewModel.project.swiftCode.outputSwiftCodeFileDirectory,
150-
isValid: $outputSwiftCodeFileDirectoryValid
165+
isValid: $outputSwiftCodeFileDirectoryValid,
166+
onDirectoryCopied: showCopiedMessage
151167
)
152168
}) {
153169
Text(.init(Lingua.ProjectForm.linguaSwiftOutputDirectoryHelp))
@@ -242,4 +258,15 @@ private extension ProjectFormView {
242258
try? firstLprojFullURL.saveBookmarkData(forKey: viewModel.project.bookmarkDataForStringsDirectory)
243259
}
244260
}
261+
262+
func showCopiedMessage() {
263+
withAnimation {
264+
copied = true
265+
}
266+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
267+
withAnimation {
268+
self.copied = false
269+
}
270+
}
271+
}
245272
}

Lingua-App/Lingua/Lingua/Utils/Components/UI/DirectoryInputField.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,45 @@ struct DirectoryInputField: View {
1313

1414
@Binding var directoryPath: String
1515
@Binding var isValid: Bool
16-
16+
@State private var isHovered = false
17+
1718
var onDirectorySelected: ((String) -> Void)? = nil
18-
19+
var onDirectoryCopied: (() -> Void)? = nil
20+
1921
var body: some View {
2022
HStack {
2123
ValidatingTextField(title: title, validation: RequiredRule(), isDisabled: true, text: $directoryPath, isValid: $isValid)
24+
.overlay {
25+
// Transparent overlay to capture tap gesture
26+
Color.clear
27+
.contentShape(Rectangle())
28+
.onTapGesture {
29+
copyPathToClipboard()
30+
onDirectoryCopied?()
31+
}
32+
}
33+
.onHover { hovering in
34+
isHovered = hovering
35+
}
2236
Button(Lingua.ProjectForm.inputDirectoryButton) {
2337
chooseDirectory()
2438
}
2539
}
2640
.padding(.vertical, 5)
41+
.background(
42+
GeometryReader { geometry in
43+
Text(directoryPath)
44+
.font(.caption)
45+
.padding(8)
46+
.background(Color.black.opacity(0.8))
47+
.foregroundColor(.white)
48+
.cornerRadius(8)
49+
.frame(width: geometry.size.width, alignment: .center)
50+
.offset(y: -geometry.size.height)
51+
.transition(.opacity)
52+
.shouldAddView(isHovered)
53+
}
54+
)
2755
}
2856
}
2957

@@ -53,4 +81,9 @@ private extension DirectoryInputField {
5381
directoryPath = ""
5482
}
5583
}
84+
85+
private func copyPathToClipboard() {
86+
NSPasteboard.general.clearContents()
87+
NSPasteboard.general.setString(directoryPath, forType: .string)
88+
}
5689
}

0 commit comments

Comments
 (0)