AGENTS.md
February 28, 2026 · View on GitHub
Testing Requirements
Tests MUST be run after every edit that modifies Swift source files.
Running Tests Locally
xcodebuild test \
-project LangSwitcher.xcodeproj \
-scheme LangSwitcher \
-destination 'platform=macOS' \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
Rules
- After every code change: Run the full test suite before considering the task complete.
- All tests must pass: Do not commit or push code with failing tests.
- New functionality requires tests: Any new feature, bug fix, or behavioral change must include corresponding unit tests.
- CI enforces tests: GitHub Actions runs
xcodebuild teston every push and pull request. Merges are blocked if tests fail.
Test Structure
- Test target:
LangSwitcherTests - Test files are in
LangSwitcherTests/ - Tests use
XCTestframework LayoutMapperandTextConverterare the primary units under testSettingsManageris@MainActor— tests that use it need@MainActorcontextTextConverteris@MainActor— also needs main actor context in tests
What to Test
LayoutMapper.convert()— character mapping between all supported layout pairs (EN, RU, UK, DE, FR, ES)LayoutMapper.detectSourceLayout()— layout detection from text contentLayoutCharacterMap.characterMap(for:)— correct pattern matching for layout IDsTextConverter.looksLikeWrongLayout()— gibberish detection (script-switch heuristic)TextConverter.findWrongLayoutBoundary()— greedy line boundary detectionTextConverter.convertLineGreedy()— greedy conversion of wrong-layout tailsTextConverter.convertSelectedText()— full conversion pipeline- Tokenization edge cases (punctuation, mixed scripts, empty strings)
Important Notes
- The Xcode project uses hand-crafted PBX IDs in the format
E1000000XXXXXXXXXXXX. New entries must follow this pattern. - LSP cross-file errors ("Cannot find in scope") are expected and NOT real errors — they resolve at Xcode build time.
"russian"contains"us"as a substring — pattern ordering inallMapsis critical. Do not reorder.
Localization Guidelines
LangSwitcher uses a custom runtime localization system (NOT Apple .lproj/.strings). All UI strings go through LocalizationManager.
Rules
- All new user-facing strings must use
LocalizationManager.shared.t("key")— never hardcode UI text. - String keys use namespace prefixes:
menu.*,settings.*,general.*,smartMode.*,layoutSwitchMode.*,layouts.*,hotkey.*,hotkeyRecorder.*,permissions.*,log.*,about.*,alert.*,common.*. - When adding a new string key: Add it to both
Strings_en.swiftandStrings_ru.swift(and any otherStrings_xx.swiftfiles). LocalizationManageris@MainActor— it is used as@EnvironmentObjectin SwiftUI views. Any code that callsLocalizationManager.shared.t()must run on the main actor.- Enum computed properties (e.g.
SmartConversionMode.displayName) that callt()must be annotated with@MainActor. - Adding a new language: Copy
Strings_en.swift→Strings_xx.swift, translate values, register inLangSwitcherApp.swift, add toavailableLanguagesinLocalizationManager.swift, add toproject.pbxproj. See README for detailed steps. - Fallback: If a key is missing in the current language,
t()falls back to English, then returns the raw key.
File Structure
LangSwitcher/Sources/Localization/
├── LocalizationManager.swift # ObservableObject, t() lookup, language persistence
├── Strings_en.swift # English strings (~113 keys)
└── Strings_ru.swift # Russian translations