Changelog-Added: Added French, German, Simplified Chinese, and Traditional Chinese translations Signed-off-by: Terry Yiu <git@tyiu.xyz>
397 lines
12 KiB
Markdown
397 lines
12 KiB
Markdown
# Notedeck Developer Documentation
|
|
|
|
This document provides technical details and guidance for developers working with the Notedeck crate.
|
|
|
|
## Architecture Overview
|
|
|
|
Notedeck is built around a modular architecture that separates concerns into distinct components:
|
|
|
|
### Core Components
|
|
|
|
1. **App Framework (`app.rs`)**
|
|
- `Notedeck` - The main application framework that ties everything together
|
|
- `App` - The trait that specific applications must implement
|
|
|
|
2. **Data Layer**
|
|
- `Ndb` - NostrDB database for efficient storage and querying
|
|
- `NoteCache` - In-memory cache for expensive-to-compute note data like nip10 structure
|
|
- `Images` - Image and GIF cache management
|
|
|
|
3. **Network Layer**
|
|
- `RelayPool` - Manages connections to Nostr relays
|
|
- `UnknownIds` - Tracks and resolves unknown profiles and notes
|
|
|
|
4. **User Accounts**
|
|
- `Accounts` - Manages user keypairs and account information
|
|
- `AccountStorage` - Handles persistent storage of account data
|
|
|
|
5. **Wallet Integration**
|
|
- `Wallet` - Lightning wallet integration
|
|
- `Zaps` - Handles Nostr zap functionality
|
|
|
|
6. **UI Components**
|
|
- `NotedeckTextStyle` - Text styling utilities
|
|
- `ColorTheme` - Theme management
|
|
- Various UI helpers
|
|
|
|
7. **Localization System**
|
|
- `LocalizationManager` - Core localization functionality
|
|
- `LocalizationContext` - Thread-safe context for sharing localization
|
|
- Fluent-based translation system
|
|
|
|
## Key Concepts
|
|
|
|
### Note Context and Actions
|
|
|
|
Notes have associated context and actions that define how users can interact with them:
|
|
|
|
```rust
|
|
pub enum NoteAction {
|
|
Reply(NoteId), // Reply to a note
|
|
Quote(NoteId), // Quote a note
|
|
Hashtag(String), // Click on a hashtag
|
|
Profile(Pubkey), // View a profile
|
|
Note(NoteId), // View a note
|
|
Context(ContextSelection), // Context menu options
|
|
Zap(ZapAction), // Zap (tip) interaction
|
|
}
|
|
```
|
|
|
|
### Relay Management
|
|
|
|
Notedeck handles relays through the `RelaySpec` structure, which implements NIP-65 functionality for marking relays as read or write.
|
|
|
|
### Filtering and Subscriptions
|
|
|
|
The `FilterState` enum manages the state of subscriptions to Nostr relays:
|
|
|
|
```rust
|
|
pub enum FilterState {
|
|
NeedsRemote(Vec<Filter>),
|
|
FetchingRemote(UnifiedSubscription),
|
|
GotRemote(Subscription),
|
|
Ready(Vec<Filter>),
|
|
Broken(FilterError),
|
|
}
|
|
```
|
|
|
|
## Development Workflow
|
|
|
|
### Setting Up Your Environment
|
|
|
|
1. Clone the repository
|
|
2. Build with `cargo build`
|
|
3. Test with `cargo test`
|
|
|
|
### Creating a New Notedeck App
|
|
|
|
1. Import the notedeck crate
|
|
2. Implement the `App` trait
|
|
3. Use the `Notedeck` struct as your application framework
|
|
|
|
Example:
|
|
|
|
```rust
|
|
use notedeck::{App, Notedeck, AppContext};
|
|
|
|
struct MyNostrApp {
|
|
// Your app-specific state
|
|
}
|
|
|
|
impl App for MyNostrApp {
|
|
fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
|
|
// Your app's UI and logic here
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let notedeck = Notedeck::new(...).app(MyNostrApp { /* ... */ });
|
|
// Run your app
|
|
}
|
|
```
|
|
|
|
### Working with Notes
|
|
|
|
Notes are the core data structure in Nostr. Here's how to work with them:
|
|
|
|
```rust
|
|
// Get a note by ID
|
|
let txn = Transaction::new(&ndb).expect("txn");
|
|
if let Ok(note) = ndb.get_note_by_id(&txn, note_id.bytes()) {
|
|
// Process the note
|
|
}
|
|
|
|
// Create a cached note
|
|
let cached_note = note_cache.cached_note_or_insert(note_key, ¬e);
|
|
```
|
|
|
|
### Adding Account Management
|
|
|
|
Account management is handled through the `Accounts` struct:
|
|
|
|
```rust
|
|
// Add a new account
|
|
let action = accounts.add_account(keypair);
|
|
action.process_action(&mut unknown_ids, &ndb, &txn);
|
|
|
|
// Get the current account
|
|
if let Some(account) = accounts.get_selected_account() {
|
|
// Use the account
|
|
}
|
|
```
|
|
|
|
## Advanced Topics
|
|
|
|
### Zaps Implementation
|
|
|
|
Notedeck implements the zap (tipping) functionality according to the Nostr protocol:
|
|
|
|
1. Creates a zap request note (kind 9734)
|
|
2. Fetches a Lightning invoice via LNURL or LUD-16
|
|
3. Pays the invoice using a connected wallet
|
|
4. Tracks the zap state
|
|
|
|
### Image Caching
|
|
|
|
The image caching system efficiently manages images and animated GIFs:
|
|
|
|
1. Downloads images from URLs
|
|
2. Stores them in a local cache
|
|
3. Handles conversion between formats
|
|
4. Manages memory usage
|
|
|
|
### Persistent Storage
|
|
|
|
Notedeck provides several persistence mechanisms:
|
|
|
|
- `AccountStorage` - For user accounts
|
|
- `TimedSerializer` - For settings that need to be saved after a delay
|
|
- Various handlers for specific settings (zoom, theme, app size)
|
|
|
|
### Localization System
|
|
|
|
Notedeck includes a comprehensive internationalization system built on the [Fluent](https://projectfluent.org/) translation framework. The system is designed for performance and developer experience.
|
|
|
|
#### Architecture
|
|
|
|
The localization system consists of several key components:
|
|
|
|
1. **LocalizationManager** - Core functionality for managing locales and translations
|
|
2. **LocalizationContext** - Thread-safe context for sharing localization across the application
|
|
3. **Fluent Resources** - Translation files in `.ftl` format stored in `assets/translations/`
|
|
|
|
#### Key Features
|
|
|
|
- **Efficient Caching**: Parsed Fluent resources and formatted strings are cached for performance
|
|
- **Thread Safety**: Uses `RwLock` for safe concurrent access
|
|
- **Dynamic Locale Switching**: Change languages at runtime without restarting
|
|
- **Argument Support**: Localized strings can include dynamic arguments
|
|
- **Development Tools**: Pseudolocale support for testing UI layout
|
|
|
|
#### Using the tr! and tr_plural! Macros
|
|
|
|
The `tr!` and `tr_plural!` macros are the primary way to use localization in Notedeck code. They provide a convenient, type-safe interface for getting localized strings.
|
|
|
|
##### The tr! Macro
|
|
|
|
```rust
|
|
use notedeck::tr;
|
|
|
|
// Simple string with comment
|
|
let welcome = tr!("Welcome to Notedeck!", "Main welcome message");
|
|
let cancel = tr!("Cancel", "Button label to cancel an action");
|
|
|
|
// String with parameters
|
|
let greeting = tr!("Hello, {name}!", "Greeting message", name="Alice");
|
|
|
|
// Multiple parameters
|
|
let message = tr!(
|
|
"Welcome {name} to {app}!",
|
|
"Welcome message with app name",
|
|
name="Alice",
|
|
app="Notedeck"
|
|
);
|
|
|
|
// In UI components
|
|
ui.button(tr!("Reply to {user}", "Reply button text", user="alice@example.com"));
|
|
```
|
|
|
|
##### The tr_plural! Macro
|
|
|
|
Use tr_plural! when there can be multiple variations of the same string depending on
|
|
some numeric count.
|
|
|
|
Not all languages follow the same pluralization rules
|
|
|
|
```rust
|
|
use notedeck::tr_plural;
|
|
|
|
// Simple pluralization
|
|
let count = 5;
|
|
let message = tr_plural!(
|
|
"You have {count} note", // Singular form
|
|
"You have {count} notes", // Plural form
|
|
"Note count message", // Comment
|
|
count // Count value
|
|
);
|
|
|
|
// With additional parameters
|
|
let user = "Alice";
|
|
let message = tr_plural!(
|
|
"{user} has {count} note", // Singular
|
|
"{user} has {count} notes", // Plural
|
|
"User note count message", // Comment
|
|
count, // Count
|
|
user=user // Additional parameter
|
|
);
|
|
```
|
|
|
|
##### Key Features
|
|
|
|
- **Automatic Key Normalization**: Converts messages and comments into valid FTL keys
|
|
- **Fallback Handling**: Falls back to original message if translation not found
|
|
- **Parameter Interpolation**: Automatically handles named parameters
|
|
- **Comment Context**: Provides context for translators
|
|
|
|
##### Best Practices
|
|
|
|
1. **Always Include Comments**: Comments provide context for translators
|
|
```rust
|
|
// Good
|
|
tr!("Add", "Button label to add something")
|
|
|
|
// Bad
|
|
tr!("Add", "")
|
|
```
|
|
|
|
2. **Use Descriptive Comments**: Make comments specific and helpful
|
|
```rust
|
|
// Good
|
|
tr!("Reply", "Button to reply to a note")
|
|
|
|
// Bad
|
|
tr!("Reply", "Reply")
|
|
```
|
|
|
|
3. **Consistent Parameter Names**: Use consistent parameter names across related strings
|
|
```rust
|
|
// Consistent
|
|
tr!("Follow {user}", "Follow button", user="alice")
|
|
tr!("Unfollow {user}", "Unfollow button", user="alice")
|
|
```
|
|
|
|
4. **Always use tr_plural! for plural strings**: Not all languages follow English pluralization rules
|
|
```rust
|
|
// Good
|
|
// Each language can have more (or less) than just two pluralization forms.
|
|
// Let the translators and the localization system help you figure that out implicitly.
|
|
let message = tr_plural!(
|
|
"You have {count} note", // Singular form
|
|
"You have {count} notes", // Plural form
|
|
"Note count message", // Comment
|
|
count // Count value
|
|
);
|
|
|
|
// Bad
|
|
// Not all languages follow pluralization rules of English.
|
|
// Some languages can have more (or less) than two variations!
|
|
if count == 1 {
|
|
tr!("You have 1 note", "Note count message")
|
|
} else {
|
|
tr!("You have {count} notes", "Note count message")
|
|
}
|
|
```
|
|
|
|
#### Translation File Format
|
|
|
|
Translation files use the [Fluent](https://projectfluent.org/) format (`.ftl`).
|
|
|
|
Developers should never create their own `.ftl` files. Whenever user-facing strings are changed in code, run `python3 scripts/export_source_strings.py`. This script will generate `assets/translations/en-US/main.ftl` and `assets/translations/en-XA/main.ftl`. The format of the files look like the following:
|
|
|
|
```ftl
|
|
# Simple string
|
|
welcome_message = Welcome to Notedeck!
|
|
|
|
# String with arguments
|
|
welcome_user = Welcome {$name}!
|
|
|
|
# String with pluralization
|
|
note_count = {$count ->
|
|
[1] One note
|
|
*[other] {$count} notes
|
|
}
|
|
```
|
|
|
|
#### Adding New Languages
|
|
|
|
TODO
|
|
|
|
#### Development with Pseudolocale (en-XA)
|
|
|
|
For testing that all user-facing strings are going through the localization system and that the
|
|
UI layout renders well with different language translations, enable the pseudolocale:
|
|
|
|
```bash
|
|
cargo run -- --debug --locale en-XA
|
|
```
|
|
|
|
The pseudolocale (`en-XA`) transforms English text in a way that is still readable but makes adjustments obvious enough that they are different from the original text (such as replacing English letters with accented equivalents), helping identify potential UI layout issues once it gets translated
|
|
to other languages.
|
|
|
|
Example transformations:
|
|
- "Add relay" → "[Àdd rélày]"
|
|
- "Cancel" → "[Çàñçél]"
|
|
- "Confirm" → "[Çóñfírm]"
|
|
|
|
#### Performance Considerations
|
|
|
|
- **Resource Caching**: Parsed Fluent resources are cached per locale
|
|
- **String Caching**: Simple strings (without arguments) are cached for repeated access
|
|
- **Cache Management**: Caches are automatically cleared when switching locales
|
|
- **Memory Limits**: String cache size can be limited to prevent memory growth
|
|
|
|
#### Testing Localization
|
|
|
|
The localization system includes comprehensive tests:
|
|
|
|
```bash
|
|
# Run localization tests
|
|
cargo test i18n
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Relay Connection Issues**
|
|
- Check network connectivity
|
|
- Verify relay URLs are correct
|
|
- Look for relay debug messages
|
|
|
|
2. **Database Errors**
|
|
- Ensure the database path is writable
|
|
- Check for database corruption
|
|
- Increase map size if needed
|
|
|
|
3. **Performance Issues**
|
|
- Monitor the frame history
|
|
- Check for large image caches
|
|
- Consider reducing the number of active subscriptions
|
|
|
|
4. **Localization Issues**
|
|
- Verify translation files exist in the correct directory structure
|
|
- Check that locale codes are valid (e.g., `en-US`, `es-ES`)
|
|
- Ensure FTL files are properly formatted
|
|
- Look for missing translation keys in logs
|
|
|
|
## Contributing
|
|
|
|
When contributing to Notedeck:
|
|
|
|
1. Follow the existing code style
|
|
2. Add tests for new functionality
|
|
3. Update documentation as needed
|
|
4. Keep performance in mind, especially for mobile targets
|
|
5. For UI changes, test with pseudolocale enabled
|
|
6. When adding new strings, ensure they are properly localized
|