Skip to content

Commit 0198e4e

Browse files
authored
Merge branch 'main' into copilot/add-camera-list-screen
2 parents 391d037 + 3f25cd8 commit 0198e4e

File tree

63 files changed

+1681
-113
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1681
-113
lines changed

.github/workflows/ci.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,59 @@ jobs:
7373
7474
The following added lines contain `// swiftlint:disable`. Please verify this is necessary.
7575
76+
check-unused-strings:
77+
needs: lint
78+
if: github.event_name == 'pull_request'
79+
runs-on: ubuntu-latest
80+
steps:
81+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
82+
83+
- name: Set up Python
84+
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.3.0
85+
with:
86+
python-version: '3.x'
87+
88+
- name: Detect unused L10n strings
89+
id: detect
90+
continue-on-error: true
91+
run: |
92+
OUTPUT=$(python3 Tools/detect_unused_strings.py 2>&1 || true)
93+
echo "output<<EOF" >> $GITHUB_OUTPUT
94+
echo "$OUTPUT" >> $GITHUB_OUTPUT
95+
echo "EOF" >> $GITHUB_OUTPUT
96+
97+
# Check if any unused strings were found
98+
if echo "$OUTPUT" | grep -q "Total unused:"; then
99+
COUNT=$(echo "$OUTPUT" | grep "Total unused:" | grep -oE '[0-9]+')
100+
echo "has_unused=true" >> $GITHUB_OUTPUT
101+
echo "count=$COUNT" >> $GITHUB_OUTPUT
102+
else
103+
echo "has_unused=false" >> $GITHUB_OUTPUT
104+
echo "count=0" >> $GITHUB_OUTPUT
105+
fi
106+
107+
- name: Comment on PR if unused strings are found
108+
if: steps.detect.outputs.has_unused == 'true'
109+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
110+
with:
111+
header: unused-strings-check
112+
message: |
113+
⚠️ **Unused L10n strings detected**
114+
115+
Found **${{ steps.detect.outputs.count }}** unused localization strings in the codebase.
116+
117+
<details>
118+
<summary>Click to see details</summary>
119+
120+
```
121+
${{ steps.detect.outputs.output }}
122+
```
123+
124+
</details>
125+
126+
Consider running `python3 Tools/remove_unused_strings.py` to clean up these strings,
127+
or use the automated cleanup workflow.
128+
76129
test:
77130
needs: check-swiftlint-disables
78131
runs-on: macos-15
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
name: Clean Unused Strings
3+
4+
on:
5+
workflow_dispatch:
6+
schedule:
7+
# Run monthly on the first day of the month at 00:00 UTC
8+
- cron: '0 0 1 * *'
9+
10+
permissions:
11+
contents: write
12+
pull-requests: write
13+
14+
jobs:
15+
clean-unused-strings:
16+
runs-on: macos-15
17+
steps:
18+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
19+
with:
20+
ssh-key: ${{ secrets.HOMEASSISTANT_SSH_DEPLOY_KEY }}
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.3.0
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Detect unused strings
28+
id: detect
29+
run: |
30+
OUTPUT=$(python3 Tools/detect_unused_strings.py 2>&1 || true)
31+
echo "output<<EOF" >> $GITHUB_OUTPUT
32+
echo "$OUTPUT" >> $GITHUB_OUTPUT
33+
echo "EOF" >> $GITHUB_OUTPUT
34+
35+
# Check if any unused strings were found
36+
if echo "$OUTPUT" | grep -q "Total unused:"; then
37+
COUNT=$(echo "$OUTPUT" | grep "Total unused:" | grep -oE '[0-9]+')
38+
echo "has_unused=true" >> $GITHUB_OUTPUT
39+
echo "count=$COUNT" >> $GITHUB_OUTPUT
40+
else
41+
echo "has_unused=false" >> $GITHUB_OUTPUT
42+
echo "count=0" >> $GITHUB_OUTPUT
43+
fi
44+
45+
- name: Skip if no unused strings
46+
if: steps.detect.outputs.has_unused != 'true'
47+
run: |
48+
echo "✅ No unused strings found - nothing to clean up!"
49+
exit 0
50+
51+
- name: Set up Ruby
52+
if: steps.detect.outputs.has_unused == 'true'
53+
uses: ruby/setup-ruby@ac793fdd38cc468a4dd57246fa9d0e868aba9085 # v1.270.0
54+
with:
55+
ruby-version: '3.1'
56+
bundler-cache: true
57+
58+
- name: Install Pods (for SwiftGen)
59+
if: steps.detect.outputs.has_unused == 'true'
60+
run: |
61+
bundle install
62+
bundle exec pod install --repo-update
63+
64+
- name: Remove unused strings
65+
if: steps.detect.outputs.has_unused == 'true'
66+
id: remove
67+
run: |
68+
python3 Tools/remove_unused_strings.py
69+
echo "removed=true" >> $GITHUB_OUTPUT
70+
71+
- name: Create Pull Request
72+
if: steps.remove.outputs.removed == 'true'
73+
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
74+
with:
75+
commit-message: |
76+
Remove unused L10n strings
77+
78+
Automatically detected and removed ${{ steps.detect.outputs.count }}
79+
unused localization strings.
80+
branch: automated/cleanup-unused-strings
81+
delete-branch: true
82+
title: 'Remove unused L10n strings'
83+
body: |
84+
## Automated Cleanup: Unused L10n Strings
85+
86+
This PR was automatically created by the `Clean Unused Strings` workflow.
87+
88+
### Summary
89+
- **Removed**: ${{ steps.detect.outputs.count }} unused localization strings
90+
- **Modified files**: All `Localizable.strings` files across language directories
91+
- **Regenerated**: `Sources/Shared/Resources/Swiftgen/Strings.swift`
92+
93+
### Details
94+
95+
<details>
96+
<summary>Unused strings that were removed</summary>
97+
98+
```
99+
${{ steps.detect.outputs.output }}
100+
```
101+
102+
</details>
103+
104+
### Review Notes
105+
- Please review the removed strings to ensure they are truly unused
106+
- The script checks for both L10n property usage and direct key usage
107+
- Strings.swift has been regenerated automatically using SwiftGen
108+
109+
---
110+
111+
*This is an automated cleanup PR. If you believe any of these strings
112+
should be kept, please comment and close this PR.*
113+
labels: |
114+
automated
115+
localization
116+
cleanup

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ env.sh
7373
.env
7474

7575
push_certs/*
76-
Tools/*.py
7776
Tools/*.ttf
7877
Tools/*.json
7978
dSYMs/

HomeAssistant.xcodeproj/project.pbxproj

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,10 @@
626626
421F2B822EF8350D00F21FE5 /* OpenCameraListAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B7F2EF8346C00F21FE5 /* OpenCameraListAppIntent.swift */; };
627627
421F2B892EF8433E00F21FE5 /* ControlOpenCamerasList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B872EF8433E00F21FE5 /* ControlOpenCamerasList.swift */; };
628628
421F2B8A2EF8433E00F21FE5 /* ControlOpenCamerasList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B872EF8433E00F21FE5 /* ControlOpenCamerasList.swift */; };
629+
421F2B9A2EF8479400F21FE5 /* ControlAutomation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B962EF8479400F21FE5 /* ControlAutomation.swift */; };
630+
421F2B9B2EF8479400F21FE5 /* ControlAutomationsValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B972EF8479400F21FE5 /* ControlAutomationsValueProvider.swift */; };
631+
421F2B9C2EF8479400F21FE5 /* IntentAutomationEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2B982EF8479400F21FE5 /* IntentAutomationEntity.swift */; };
632+
421F2BA12EF847AA00F21FE5 /* AutomationAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421F2BA02EF847AA00F21FE5 /* AutomationAppIntent.swift */; };
629633
4221ED352D009EF700BAE3EB /* AppDatabaseUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4221ED332D009BD000BAE3EB /* AppDatabaseUpdater.swift */; };
630634
4223688B2D40F9B7005911E4 /* WidgetCustom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4223688A2D40F9B7005911E4 /* WidgetCustom.swift */; };
631635
4223688E2D40FB00005911E4 /* WidgetCustomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4223688D2D40FB00005911E4 /* WidgetCustomTimelineProvider.swift */; };
@@ -2196,6 +2200,10 @@
21962200
421F2B7B2EF8346400F21FE5 /* CameraListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraListViewModel.swift; sourceTree = "<group>"; };
21972201
421F2B7F2EF8346C00F21FE5 /* OpenCameraListAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCameraListAppIntent.swift; sourceTree = "<group>"; };
21982202
421F2B872EF8433E00F21FE5 /* ControlOpenCamerasList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlOpenCamerasList.swift; sourceTree = "<group>"; };
2203+
421F2B962EF8479400F21FE5 /* ControlAutomation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlAutomation.swift; sourceTree = "<group>"; };
2204+
421F2B972EF8479400F21FE5 /* ControlAutomationsValueProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlAutomationsValueProvider.swift; sourceTree = "<group>"; };
2205+
421F2B982EF8479400F21FE5 /* IntentAutomationEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentAutomationEntity.swift; sourceTree = "<group>"; };
2206+
421F2BA02EF847AA00F21FE5 /* AutomationAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationAppIntent.swift; sourceTree = "<group>"; };
21992207
4221ED332D009BD000BAE3EB /* AppDatabaseUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabaseUpdater.swift; sourceTree = "<group>"; };
22002208
4223688A2D40F9B7005911E4 /* WidgetCustom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetCustom.swift; sourceTree = "<group>"; };
22012209
4223688D2D40FB00005911E4 /* WidgetCustomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetCustomTimelineProvider.swift; sourceTree = "<group>"; };
@@ -4588,6 +4596,15 @@
45884596
421F2B872EF8433E00F21FE5 /* ControlOpenCamerasList.swift */,
45894597
);
45904598
path = CameraList;
4599+
421F2B992EF8479400F21FE5 /* Automation */ = {
4600+
isa = PBXGroup;
4601+
children = (
4602+
421F2BA02EF847AA00F21FE5 /* AutomationAppIntent.swift */,
4603+
421F2B962EF8479400F21FE5 /* ControlAutomation.swift */,
4604+
421F2B972EF8479400F21FE5 /* ControlAutomationsValueProvider.swift */,
4605+
421F2B982EF8479400F21FE5 /* IntentAutomationEntity.swift */,
4606+
);
4607+
path = Automation;
45914608
sourceTree = "<group>";
45924609
};
45934610
422368872D40F959005911E4 /* Custom */ = {
@@ -4604,6 +4621,7 @@
46044621
isa = PBXGroup;
46054622
children = (
46064623
421F2B882EF8433E00F21FE5 /* CameraList */,
4624+
421F2B992EF8479400F21FE5 /* Automation */,
46074625
420396382EF16A4700C9DF74 /* OpenEntity */,
46084626
4203961A2EF16A4700C9DF74 /* Button */,
46094627
42C101252CD3DAAA0012BA78 /* Cover */,
@@ -8449,6 +8467,7 @@
84498467
110E694424E77125004AA96D /* WidgetActionsProvider.swift in Sources */,
84508468
42BA1BC82C8864C200A2FC36 /* OpenPageAppIntent.swift in Sources */,
84518469
426CBB6A2C9C543F003CA3AC /* ControlSwitchValueProvider.swift in Sources */,
8470+
421F2BA12EF847AA00F21FE5 /* AutomationAppIntent.swift in Sources */,
84528471
42BF7F302DF867E600875A0F /* HAAppEntityAppIntentEntity.swift in Sources */,
84538472
420E2AE52C4746CD004921D8 /* WidgetBasicSizeStyle.swift in Sources */,
84548473
42B74A632D36B08600C37304 /* WidgetBasicView.swift in Sources */,
@@ -8467,6 +8486,9 @@
84678486
424A7F462B188946008C8DF3 /* WidgetBackground.swift in Sources */,
84688487
4008F0262C2D0A1A00E24001 /* WidgetCircularView.swift in Sources */,
84698488
42F958992BB4684700497981 /* WidgetAssist.swift in Sources */,
8489+
421F2B9A2EF8479400F21FE5 /* ControlAutomation.swift in Sources */,
8490+
421F2B9B2EF8479400F21FE5 /* ControlAutomationsValueProvider.swift in Sources */,
8491+
421F2B9C2EF8479400F21FE5 /* IntentAutomationEntity.swift in Sources */,
84708492
3E4087F12CEC7F210085DF29 /* WidgetBasicSensorView.swift in Sources */,
84718493
3E02C0F52CA8047000102131 /* WidgetSensorsAppIntent.swift in Sources */,
84728494
4080D5C52C319B0A00099C88 /* WidgetDetailsAppIntentTimelineProvider.swift in Sources */,

Sources/App/Resources/bg.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@
6363
"app_intents.controls.assist.parameter.with_voice" = "With voice";
6464
"app_intents.controls.assist.title" = "Assist in app";
6565
"app_intents.cover.title" = "Cover";
66+
"app_intents.fan.off_state_icon.title" = "Icon for off state";
67+
"app_intents.fan.on_state_icon.title" = "Icon for on state";
68+
"app_intents.fan.title" = "Fan";
6669
"app_intents.haptic_confirmation.title" = "Haptic confirmation";
6770
"app_intents.icon.title" = "Icon";
6871
"app_intents.intent.cover.title" = "Control cover";
72+
"app_intents.intent.fan.title" = "Control fan";
6973
"app_intents.intent.light.title" = "Control light";
7074
"app_intents.intent.switch.title" = "Control switch";
7175
"app_intents.lights.light.target" = "Target state";
@@ -1274,6 +1278,9 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho
12741278
"widgets.controls.cover.description" = "Toggle cover";
12751279
"widgets.controls.cover.placeholder_title" = "Choose cover";
12761280
"widgets.controls.cover.title" = "Cover";
1281+
"widgets.controls.fan.description" = "Turn on/off your fan";
1282+
"widgets.controls.fan.placeholder_title" = "Choose fan";
1283+
"widgets.controls.fan.title" = "Fan";
12771284
"widgets.controls.light.description" = "Turn on/off your light";
12781285
"widgets.controls.light.placeholder_title" = "Choose Light";
12791286
"widgets.controls.light.title" = "Light";

Sources/App/Resources/ca-ES.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@
6363
"app_intents.controls.assist.parameter.with_voice" = "With voice";
6464
"app_intents.controls.assist.title" = "Assist in app";
6565
"app_intents.cover.title" = "Cover";
66+
"app_intents.fan.off_state_icon.title" = "Icon for off state";
67+
"app_intents.fan.on_state_icon.title" = "Icon for on state";
68+
"app_intents.fan.title" = "Fan";
6669
"app_intents.haptic_confirmation.title" = "Haptic confirmation";
6770
"app_intents.icon.title" = "Icon";
6871
"app_intents.intent.cover.title" = "Control cover";
72+
"app_intents.intent.fan.title" = "Control fan";
6973
"app_intents.intent.light.title" = "Control light";
7074
"app_intents.intent.switch.title" = "Control switch";
7175
"app_intents.lights.light.target" = "Target state";
@@ -1274,6 +1278,9 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho
12741278
"widgets.controls.cover.description" = "Toggle cover";
12751279
"widgets.controls.cover.placeholder_title" = "Choose cover";
12761280
"widgets.controls.cover.title" = "Cover";
1281+
"widgets.controls.fan.description" = "Turn on/off your fan";
1282+
"widgets.controls.fan.placeholder_title" = "Choose fan";
1283+
"widgets.controls.fan.title" = "Fan";
12771284
"widgets.controls.light.description" = "Turn on/off your light";
12781285
"widgets.controls.light.placeholder_title" = "Choose Light";
12791286
"widgets.controls.light.title" = "Light";

0 commit comments

Comments
 (0)