Skip to content

Commit 5710080

Browse files
committed
translation improvements
chinese conversion, storing specific source language for a specific song in coredata, storing target translation language in coredata
1 parent 0829a86 commit 5710080

File tree

13 files changed

+339
-77
lines changed

13 files changed

+339
-77
lines changed

Localizable.xcstrings

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@
100100
},
101101
"Artist Name:" : {
102102

103+
},
104+
"Auto" : {
105+
103106
},
104107
"Back" : {
105108
"localizations" : {
@@ -126,6 +129,9 @@
126129
}
127130
}
128131
}
132+
},
133+
"Chinese Conversion" : {
134+
129135
},
130136
"Click to Use" : {
131137

@@ -780,6 +786,9 @@
780786
},
781787
"Song Name:" : {
782788

789+
},
790+
"Source Language" : {
791+
783792
},
784793
"Spotify" : {
785794
"localizations" : {
@@ -813,6 +822,12 @@
813822
}
814823
}
815824
}
825+
},
826+
"System (%@)" : {
827+
828+
},
829+
"Target Language" : {
830+
816831
},
817832
"This depends on how much free space you have in your menu bar!" : {
818833
"localizations" : {
@@ -878,9 +893,21 @@
878893
},
879894
"Translation options" : {
880895

896+
},
897+
"Translation Options" : {
898+
899+
},
900+
"Translation Settings for All Songs" : {
901+
902+
},
903+
"Translation Settings for This Song" : {
904+
881905
},
882906
"Translation Unavailable" : {
883907

908+
},
909+
"Transliteration Options" : {
910+
884911
},
885912
"Truncation Length" : {
886913
"localizations" : {

Lyric Fever.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
8F1979972DA72D49008C113D /* Mecab-Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 8F1979962DA72D49008C113D /* Mecab-Swift */; };
1212
8F28BFD42D578456002E2CE6 /* KaraokeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F28BFD32D578456002E2CE6 /* KaraokeSettings.swift */; };
1313
8F28FDC22AA25D2700439D8D /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 8F28FDC12AA25D2700439D8D /* Sparkle */; };
14+
8F37323D2E6EC9C90075CDA7 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 8F37323C2E6EC9C90075CDA7 /* OpenCC */; };
1415
8F4F49522A7EC32D00097888 /* (null) in Sources */ = {isa = PBXBuildFile; };
1516
8F6BD2952A8A61C9008BBF88 /* AmplitudeSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8F6BD2942A8A61C9008BBF88 /* AmplitudeSwift */; };
1617
8F6BD2972A8A6278008BBF88 /* amplitudeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6BD2962A8A6278008BBF88 /* amplitudeKey.swift */; };
@@ -69,6 +70,7 @@
6970
isa = PBXFrameworksBuildPhase;
7071
buildActionMask = 2147483647;
7172
files = (
73+
8F37323D2E6EC9C90075CDA7 /* OpenCC in Frameworks */,
7274
8F1979952DA72D49008C113D /* IPADic in Frameworks */,
7375
8F1979972DA72D49008C113D /* Mecab-Swift in Frameworks */,
7476
8FFED07B2D8A26E700EA1510 /* SwiftOTP in Frameworks */,
@@ -172,6 +174,7 @@
172174
8F1979962DA72D49008C113D /* Mecab-Swift */,
173175
8F7C7EE72E34338900CA9041 /* ObservableDefaults */,
174176
8FFB32DE2E694991007D9981 /* CompactSlider */,
177+
8F37323C2E6EC9C90075CDA7 /* OpenCC */,
175178
);
176179
productName = SpotifyLyricsInMenubar;
177180
productReference = 8FC8E9452A704EEB00F69915 /* Lyric Fever.app */;
@@ -214,6 +217,7 @@
214217
8F1979932DA72D49008C113D /* XCRemoteSwiftPackageReference "Mecab-Swift" */,
215218
8F7C7EE62E34338900CA9041 /* XCRemoteSwiftPackageReference "ObservableDefaults" */,
216219
8FFB32DD2E694991007D9981 /* XCRemoteSwiftPackageReference "CompactSlider" */,
220+
8F37323B2E6EC9C90075CDA7 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */,
217221
);
218222
productRefGroup = 8FC8E9462A704EEB00F69915 /* Products */;
219223
projectDirPath = "";
@@ -497,6 +501,14 @@
497501
minimumVersion = 2.4.2;
498502
};
499503
};
504+
8F37323B2E6EC9C90075CDA7 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */ = {
505+
isa = XCRemoteSwiftPackageReference;
506+
repositoryURL = "https://github.com/ddddxxx/SwiftyOpenCC";
507+
requirement = {
508+
branch = master;
509+
kind = branch;
510+
};
511+
};
500512
8F6BD2932A8A61C8008BBF88 /* XCRemoteSwiftPackageReference "Amplitude-Swift" */ = {
501513
isa = XCRemoteSwiftPackageReference;
502514
repositoryURL = "https://github.com/amplitude/Amplitude-Swift";
@@ -587,6 +599,11 @@
587599
package = 8F28FDC02AA25D2700439D8D /* XCRemoteSwiftPackageReference "Sparkle" */;
588600
productName = Sparkle;
589601
};
602+
8F37323C2E6EC9C90075CDA7 /* OpenCC */ = {
603+
isa = XCSwiftPackageProductDependency;
604+
package = 8F37323B2E6EC9C90075CDA7 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */;
605+
productName = OpenCC;
606+
};
590607
8F6BD2942A8A61C9008BBF88 /* AmplitudeSwift */ = {
591608
isa = XCSwiftPackageProductDependency;
592609
package = 8F6BD2932A8A61C8008BBF88 /* XCRemoteSwiftPackageReference "Amplitude-Swift" */;

LyricFever/LyricFever.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ struct LyricFever: App {
104104
.onReceive(NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification)) { _ in
105105
viewmodel.saveKaraokeFontOnTermination()
106106
}
107+
.onChange(of: viewmodel.translationSourceLanguage) {
108+
// don't call reloadTranslationConfigIfTranslating(), that invalidates when config is the same
109+
if viewmodel.userDefaultStorage.translate {
110+
viewmodel.translationSessionConfig = TranslationSession.Configuration(source: viewmodel.translationSourceLanguage, target: viewmodel.userLocaleLanguage)
111+
}
112+
}
113+
.onChange(of: viewmodel.userLocaleLanguage) {
114+
viewmodel.reloadTranslationConfigIfTranslating()
115+
}
116+
.onChange(of: viewmodel.userDefaultStorage.chinesePreference) {
117+
viewmodel.chinesePreferenceDidChange()
118+
}
107119
.onChange(of: viewmodel.userDefaultStorage.romanize) {
108120
viewmodel.romanizeDidChange()
109121
}

LyricFever/LyricProvider/Spotify/SpotifyLyricProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class SpotifyLyricProvider: LyricProvider {
6363

6464
init() {
6565
// Set user agents for Spotify and LRCLIB
66-
fakeSpotifyUserAgentconfig.httpAdditionalHeaders = ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15"]
66+
fakeSpotifyUserAgentconfig.httpAdditionalHeaders = ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 15_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15"]
6767
fakeSpotifyUserAgentSession = URLSession(configuration: fakeSpotifyUserAgentconfig)
6868
}
6969

LyricFever/Models/CoreData/Lyrics.xcdatamodeld/Lyrics.xcdatamodel/contents

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24A335" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
2+
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="24299" systemVersion="25A5349a" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
33
<entity name="IDToColor" representedClassName="IDToColor" syncable="YES" codeGenerationType="class">
44
<attribute name="id" attributeType="String"/>
55
<attribute name="songColor" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
@@ -31,4 +31,13 @@
3131
</uniquenessConstraint>
3232
</uniquenessConstraints>
3333
</entity>
34+
<entity name="SongToLocale" representedClassName="SongToLocale" syncable="YES" codeGenerationType="class">
35+
<attribute name="id" attributeType="String"/>
36+
<attribute name="locale" attributeType="String"/>
37+
<uniquenessConstraints>
38+
<uniquenessConstraint>
39+
<constraint value="id"/>
40+
</uniquenessConstraint>
41+
</uniquenessConstraints>
42+
</entity>
3443
</model>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// ChineseConversion.swift
3+
// Lyric Fever
4+
//
5+
// Created by Avi Wadhwa on 2025-09-08.
6+
//
7+
8+
enum ChineseConversion: Int, CaseIterable, Identifiable {
9+
case none = 0
10+
case simplified
11+
case traditionalNeutral
12+
case traditionalTaiwan
13+
case traditionalHK
14+
15+
var id: Self {
16+
return self
17+
}
18+
19+
var description: String {
20+
switch self {
21+
case .none:
22+
"None"
23+
case .simplified:
24+
"Simplified"
25+
case .traditionalNeutral:
26+
"Traditional (Neutral)"
27+
case .traditionalTaiwan:
28+
"Traditional (Taiwan)"
29+
case .traditionalHK:
30+
"Traditional (HK)"
31+
}
32+
}
33+
}

LyricFever/Services/RomanizerService/RomanizerService.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import NaturalLanguage
1717
import Mecab_Swift
1818
import IPADic
19+
import OpenCC
1920

2021
class RomanizerService {
2122
private static func generateJapaneseRomanizedLyric(_ lyric: LyricLine) -> String? {
@@ -29,10 +30,51 @@ class RomanizerService {
2930
return romanized
3031
}
3132
static func generateRomanizedLyric(_ lyric: LyricLine) -> String? {
33+
print("Generating Romanized String for lyric \(lyric.words)")
3234
if let language = NLLanguageRecognizer.dominantLanguage(for: lyric.words), language == .japanese {
3335
return generateJapaneseRomanizedLyric(lyric)
3436
} else {
3537
return lyric.words.applyingTransform(.toLatin, reverse: false)
3638
}
3739
}
40+
41+
static func generateMainlandTransliteration(_ lyric: LyricLine) -> String? {
42+
do {
43+
let converter = try ChineseConverter(options: [.simplify])
44+
return converter.convert(lyric.words)
45+
} catch {
46+
print("RomanizerService: MainlandTransliteration error: \(error)")
47+
return nil
48+
}
49+
}
50+
51+
static func generateTraditionalNeutralTransliteration(_ lyric: LyricLine) -> String? {
52+
do {
53+
let converter = try ChineseConverter(options: [.traditionalize])
54+
return converter.convert(lyric.words)
55+
} catch {
56+
print("RomanizerService: MainlandTransliteration error: \(error)")
57+
return nil
58+
}
59+
}
60+
61+
static func generateHongKongTransliteration(_ lyric: LyricLine) -> String? {
62+
do {
63+
let converter = try ChineseConverter(options: [.traditionalize, .hkStandard])
64+
return converter.convert(lyric.words)
65+
} catch {
66+
print("RomanizerService: HongKongTransliteration error: \(error)")
67+
return nil
68+
}
69+
}
70+
71+
static func generateTaiwanTransliteration(_ lyric: LyricLine) -> String? {
72+
do {
73+
let converter = try ChineseConverter(options: [.traditionalize, .twStandard, .twIdiom])
74+
return converter.convert(lyric.words)
75+
} catch {
76+
print("RomanizerService: TaiwanTransliteration error: \(error)")
77+
return nil
78+
}
79+
}
3880
}

LyricFever/Support Files/UserDefaultStorage.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ import Combine
99
import SwiftUI
1010
import ObservableDefaults
1111

12+
extension Locale.Language: @retroactive UserDefaultsPropertyListValue {}
13+
extension Locale.Language: @retroactive CodableUserDefaultsPropertyListValue {
14+
15+
}
16+
1217
@ObservableDefaults
1318
class UserDefaultStorage {
1419
var translate: Bool = false
20+
var translationTargetLanguage: Locale.Language? = nil
1521
#if os(macOS)
1622
var showSongDetailsInMenubar: Bool = true
1723
#endif
1824
var blurFullscreen: Bool = true
1925
var animateOnStartupFullscreen: Bool = true
2026
var romanize: Bool = false
27+
var chinesePreference: Int = 0
2128
#if os(macOS)
2229
var spotifyConnectDelayCount: Int = 400
2330
var hasMigrated: Bool = false

0 commit comments

Comments
 (0)