Skip to content

Commit d94f2ae

Browse files
authored
Add CLAUDE.md (#24594)
* Add CLAUDE.md * Rewrite Localizatation.md
1 parent ec62419 commit d94f2ae

File tree

3 files changed

+144
-137
lines changed

3 files changed

+144
-137
lines changed

CLAUDE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
WordPress for iOS is the official mobile app for WordPress that lets users create, manage, and publish content to their WordPress websites directly from their iPhone or iPad.
8+
9+
Minimum requires iOS version is iOS 16.
10+
11+
## Common Development Commands
12+
13+
### Build & Dependencies
14+
- `rake build` - Build the app
15+
- `xcodebuild -scheme <target> -destination 'platform=iOS Simulator,name=iPhone 16' | bundle exec xcpretty` build targets from `Modules/`.
16+
17+
### Testing
18+
- `rake test` - Run all tests
19+
20+
### Code Quality
21+
- `rake lint` - Check for SwiftLint errors
22+
23+
## High-Level Architecture
24+
25+
### Project Structure
26+
WordPress-iOS uses a modular architecture with the main app and separate Swift packages:
27+
28+
- **Main App**: `WordPress/Classes/` - core app functionality
29+
- **Modules**: `Modules/Sources/` - Reusable Swift packages including:
30+
- `WordPressUI` - shared UI components
31+
- `WordPressFlux` - deprecated state management using Flux pattern (DO NOT USE)
32+
- `WordPressKit` - API client and networking
33+
- `WordPressShared` - Shared utilities
34+
- `DesignSystem` - design system
35+
36+
### Key Patterns
37+
- **Architecture**: SwiftUI with MVVM for new features
38+
- **ViewModels**: Use `@MainActor` class conforming to `ObservableObject` with `@Published` properties
39+
- **Concurrency**: Swift async/await patterns with `@MainActor` for UI thread safety
40+
- **Navigation**: SwiftUI NavigationStack
41+
- **Persistence**: Core Data with `@FetchRequest` for SwiftUI integration
42+
- **UI**: Progressive SwiftUI adoption using `UIHostingController` bridge pattern
43+
- **Dependency Injection**: Constructor injection with protocol-based services
44+
45+
#### Testing Patterns
46+
- Use Swift Testing for new tests
47+
48+
### Important Considerations
49+
- **Multi-site Support**: Code must handle both WordPress.com and self-hosted sites
50+
- **Accessibility**: Use proper accessibility labels and traits
51+
- **Localization**: follow best practices from @docs/localization.md
52+
53+
## Coding Standards
54+
- Follow Swift API Design Guidelines
55+
- Use strict access control modifiers where possible
56+
- Use four spaces (not tabs)
57+
58+
### Development Workflow
59+
- Branch from `trunk` (main branch)
60+
- PR target should be `trunk`
61+
- When writing commit messages, never include references to Claude

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ end
643643

644644
def xcodebuild(*build_cmds)
645645
cmd = 'xcodebuild'
646-
cmd += " -destination 'platform=iOS Simulator,name=iPhone 6s'"
646+
cmd += " -destination 'platform=iOS Simulator,name=iPhone 16'"
647647
cmd += ' -sdk iphonesimulator'
648648
cmd += " -workspace #{XCODE_WORKSPACE}"
649649
cmd += " -scheme #{XCODE_SCHEME}"

docs/localization.md

Lines changed: 82 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,113 @@
11
# Localization
22

3-
## Technical considerations
3+
Use `NSLocalizedString()` for all user-facing text. During release, strings are automatically extracted and uploaded to GlotPress for translation. The developes do not need to edit `Localizable.strings`.
44

5-
During development, using [`NSLocalizedString()`](https://developer.apple.com/documentation/foundation/nslocalizedstring) in the code should be enough. You shouldn't need to touch the `Localizable.strings` files manually.
5+
## Key Rules
66

7-
During the release process, `NSLocalizedString` statements are scanned and stored in the `Localizable.strings` file. The file is then uploaded to [GlotPress](https://translate.wordpress.org/projects/apps/ios/) for translation. Before the release build is finalized, all the translations are grabbed from GlotPress and saved back to the `Localizable.strings` files.
7+
1. **Use reverse-DNS keys**: `"editor.post.buttonTitle"` not `"Post"`
8+
2. **Always add meaningful comments**: Describe context and placeholders
9+
3. **Use positional placeholders**: `%1$@`, `%2$d` not just `%@`, `%d`
10+
4. **No variables in NSLocalizedString**: All parameters must be string literals
11+
5. **Use String.localizedStringWithFormat**: Don't use string interpolation
812

9-
### Use unique reverse-DNS naming style Keys
10-
11-
Use unique reverse-DNS naming style for keys of localized strings (instead of using the English copy as key). This allows to avoid issues where the same word in English could need different translations based on context, or very long keys causing issues with some translation services.
12-
13-
```swift
14-
// Do
15-
let postBtnTitle = NSLocalizedString("editor.post.buttonTitle", value: "Post", comment: "Verb. Action to publish a post")
16-
let postType = NSLocalizedString("reader.post.title", value: "Post", comment: "Noun. Describes when an entry is a blog post (and not story or page)"
17-
```
18-
19-
```swift
20-
// Don't
21-
let postBtnTitle = NSLocalizedString("Post", comment: "Verb. Action to publish a post")
22-
let postType = NSLocalizedString("Post", comment: "Noun. Describes when an entry is a blog post (and not story or page)"
23-
```
24-
25-
### Always add Comments
26-
27-
Always add a meaningful comment. If possible, describe where and how the string will be used. If there are placeholders, describe what each placeholder is.
13+
## Examples
2814

15+
### Basic Usage
2916
```swift
30-
// Do
31-
let succeededMessage = String(format: NSLocalizedString(
32-
"reader.post.follow.successTitle",
33-
value: "Following %1$@",
34-
comment: "Notice title when following a site succeeds. %1$@ is a placeholder for the site name."
35-
), siteName)
36-
```
37-
38-
```swift
39-
// Don't
40-
let succeededMessage = String(format: NSLocalizedString(
41-
"reader.post.follow.successTitle",
42-
value: "Following %1$@",
43-
comment: ""
44-
), siteName)
45-
```
46-
47-
Comments help give more context to translators.
48-
49-
### Use positional placeholders
50-
51-
Use the `%n$x` format (with `n` being an integer for the parameter position/index in the arguments to `String(format:)`, and `x` being one of the type specifiers like `@` or `d`); in particular, don't use just `%x` (the one without explicit positional index) for positional placeholders. This way, translators will not risk of messing up the parameter resolution order when translating the copy in locales where the order of the words in the sentence might be different than the one in English.
52-
53-
```swift
54-
// Do
55-
let alertWarning = String(format: NSLocalizedString(
56-
"login.email.locationWarning",
57-
value: "Are you trying to log in to %1$@ near %2$@?",
58-
comment: "Login location warning alert. %1$@ is an account name and %2$@ is a location name."
59-
), accountName, locationName)
60-
```
61-
62-
```swift
63-
// Don't
64-
let alertWarning = String(format: NSLocalizedString(
65-
"login.email.locationWarning",
66-
value: "Are you trying to log in to %@ near %@?",
67-
comment: "Login location warning alert."
68-
), accountName, locationName)
69-
```
70-
71-
### Do not use Variables
72-
73-
Do not use variables as the argument of `NSLocalizedString()` (neither for the key, the value or the comment). The string key, value and comment will not be automatically picked up by the `genstrings` tool which expects string literals.
17+
private enum Strings {
18+
static let title = NSLocalizedString(
19+
"settings.screen.title",
20+
value: "Settings",
21+
comment: "Title for the settings screen"
22+
)
23+
24+
static let saveButton = NSLocalizedString(
25+
"settings.save.button",
26+
value: "Save Changes",
27+
comment: "Button to save settings changes"
28+
)
29+
}
7430

75-
```swift
76-
// Do
77-
let myText = NSLocalizedString("someScreen.title", value: "This is the text I want to translate.", comment: "Put a meaningful comment here.")
78-
myTextLabel?.text = myText
31+
// Usage
32+
Text(Strings.title)
33+
Button(Strings.saveButton) { /* action */ }
7934
```
8035

36+
### With Placeholders
8137
```swift
82-
// Don't
83-
let myText = "This is the text I want to translate."
84-
myTextLabel?.text = NSLocalizedString("someScreen.title", value: myText, comment: "Put a meaningful comment here.")
85-
let myKey = "some.place.title"
86-
myTextLabel?.text = NSLocalizedString(myKey, value: "This is the text I want to translate.", comment: "Put a meaningful comment here.")
87-
let comment = "Put a meaningful comment here."
88-
myTextLabel?.text = NSLocalizedString("someScreen.title", value: "This is the text I want to translate.", comment: comment)
89-
```
90-
91-
### Do not use Interpolated Strings
92-
93-
Interpolated strings are harder to understand by translators and they may end up translating/changing the variable name, causing a crash.
94-
95-
Use [`String.localizedStringWithFormat`](https://developer.apple.com/documentation/swift/string/1414192-localizedstringwithformat) instead.
38+
private enum Strings {
39+
static let welcomeMessage = NSLocalizedString(
40+
"dashboard.welcome.message",
41+
value: "Welcome back, %1$@!",
42+
comment: "Welcome message on dashboard. %1$@ is the user's name."
43+
)
44+
45+
static let postCount = NSLocalizedString(
46+
"dashboard.post.count",
47+
value: "You have %1$d posts in %2$@",
48+
comment: "Post count message. %1$d is number of posts, %2$@ is site name."
49+
)
50+
}
9651

97-
```swift
98-
// Do
99-
let year = 2019
100-
let template = NSLocalizedString("mySite.copyrightNotice.title", value: "© %d Acme, Inc.", comment: "Copyright Notice")
101-
let str = String.localizeStringWithFormat(template, year)
52+
// Usage
53+
let welcome = String.localizedStringWithFormat(Strings.welcomeMessage, userName)
54+
let count = String.localizedStringWithFormat(Strings.postCount, postCount, siteName)
10255
```
10356

57+
### Pluralization
10458
```swift
105-
// Don't
106-
let year = 2019
107-
let str = NSLocalizedString("mySite.copyrightNotice.title", value: "© \(year) Acme, Inc.", comment: "Copyright Notice")
108-
```
109-
110-
### Multiline Strings
111-
112-
For readability, you can split the string and concatenate the parts using the plus (`+`) symbol.
59+
private enum Strings {
60+
static let postCountSingular = NSLocalizedString(
61+
"posts.count.singular",
62+
value: "%1$d post",
63+
comment: "Number of posts (singular). %1$d is the count."
64+
)
65+
66+
static let postCountPlural = NSLocalizedString(
67+
"posts.count.plural",
68+
value: "%1$d posts",
69+
comment: "Number of posts (plural). %1$d is the count."
70+
)
71+
}
11372

114-
```swift
115-
// Okay
116-
NSLocalizedString(
117-
"someScreen.concatenatedDescription",
118-
value: "Take some long text here " +
119-
"and then concatenate it using the '+' symbol."
120-
comment: "You can even use this form of concatenation " +
121-
"for extra-long comments that take the time to explain " +
122-
"lots of details to help our translators make accurate translations."
123-
)
73+
// Usage
74+
let template = count == 1 ? Strings.postCountSingular : Strings.postCountPlural
75+
let text = String.localizedStringWithFormat(template, count)
12476
```
12577

126-
Do not use extended delimiters (e.g. triple quotes). They are not automatically picked up.
78+
### Shared Strings
79+
Use `SharedStrings` (@WordPress/Classes/Utility/SharedStrings.swift) for common UI elements like "Cancel", "Done", "Save".
12780

81+
### Numbers
12882
```swift
129-
// Don't
130-
NSLocalizedString(
131-
"someScreen.tripleQuotedDescription",
132-
"""Triple-quoted text, when used in NSLocalizedString, is Not OK. Our scripts break when you use this."""
133-
comment: """Triple-quoted text, when used in NSLocalizedString, is Not OK."""
134-
)
83+
let localizedCount = NumberFormatter.localizedString(from: NSNumber(value: count), number: .none)
13584
```
13685

137-
### Shared Strings
138-
139-
Use `SharedStrings` for localizable strings used across many screens like button title "Cancel".
140-
141-
### Pluralization
86+
## Organization Pattern
14287

143-
GlotPress currently does not support pluralization using the [`.stringsdict` file](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LocalizingYourApp/LocalizingYourApp.html#//apple_ref/doc/uid/10000171i-CH5-SW10). So, right now, you have to support plurals manually by having separate localized strings.
88+
Organize strings using private enums within each view or view model:
14489

145-
This is not an ideal situation, and in the future we're hoping to properly support real pluralization with `.stringdict` files, which takes into account the complexity of different locales having different pluralization rules (sometimes way more complex than the simple singular/plural rule that English has, e.g. like [in Irish](https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html#ga)).
146-
147-
In the meantime, we sadly have to make-do with the simplistic solution of providing two different localized strings and doing the pluralization decision by code, even if that will lead to some inexact pluralization in some locales.
14890
```swift
149-
struct PostCountLabels {
150-
static let singular = NSLocalizedString("reader.post.title" ,value: "%d Post", comment: "Number of posts displayed in Posting Activity when a day is selected. %d will contain the actual number (singular).")
151-
static let plural = NSLocalizedString("reader.postList.title", value: "%d Posts", comment: "Number of posts displayed in Posting Activity when a day is selected. %d will contain the actual number (plural).")
91+
struct PostEditorView: View {
92+
var body: some View {
93+
NavigationView {
94+
TextEditor(text: $postContent)
95+
.placeholder(Strings.placeholder)
96+
.navigationTitle(Strings.title)
97+
}
98+
}
15299
}
153100

154-
let postCountText = (count == 1 ? PostCountLabels.singular : PostCountLabels.plural)
155-
```
156-
157-
### Numbers
158-
159-
Localize numbers whenever possible.
101+
private enum Strings {
102+
static let title = NSLocalizedString("editor.title", value: "New Post", comment: "Editor screen title")
103+
static let placeholder = NSLocalizedString("editor.placeholder", value: "Start writing...", comment: "Editor text placeholder")
104+
}
160105

161-
```swift
162-
let localizedCount = NumberFormatter.localizedString(from: NSNumber(value: count), number: .none)
106+
#Preview {
107+
PostEditorView()
108+
}
163109
```
164110

165-
## Testing considerations
111+
## Testing
166112

167-
Test changes that include localized content by using large words or with letters/accents not frequently used in English.
113+
Test with long words and special characters to ensure UI layouts work across languages.

0 commit comments

Comments
 (0)