Changelog-Added: Added French, German, Simplified Chinese, and Traditional Chinese translations Signed-off-by: Terry Yiu <git@tyiu.xyz>
12 KiB
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
-
App Framework (
app.rs)Notedeck- The main application framework that ties everything togetherApp- The trait that specific applications must implement
-
Data Layer
Ndb- NostrDB database for efficient storage and queryingNoteCache- In-memory cache for expensive-to-compute note data like nip10 structureImages- Image and GIF cache management
-
Network Layer
RelayPool- Manages connections to Nostr relaysUnknownIds- Tracks and resolves unknown profiles and notes
-
User Accounts
Accounts- Manages user keypairs and account informationAccountStorage- Handles persistent storage of account data
-
Wallet Integration
Wallet- Lightning wallet integrationZaps- Handles Nostr zap functionality
-
UI Components
NotedeckTextStyle- Text styling utilitiesColorTheme- Theme management- Various UI helpers
-
Localization System
LocalizationManager- Core localization functionalityLocalizationContext- 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:
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:
pub enum FilterState {
NeedsRemote(Vec<Filter>),
FetchingRemote(UnifiedSubscription),
GotRemote(Subscription),
Ready(Vec<Filter>),
Broken(FilterError),
}
Development Workflow
Setting Up Your Environment
- Clone the repository
- Build with
cargo build - Test with
cargo test
Creating a New Notedeck App
- Import the notedeck crate
- Implement the
Apptrait - Use the
Notedeckstruct as your application framework
Example:
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:
// 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:
// 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:
- Creates a zap request note (kind 9734)
- Fetches a Lightning invoice via LNURL or LUD-16
- Pays the invoice using a connected wallet
- Tracks the zap state
Image Caching
The image caching system efficiently manages images and animated GIFs:
- Downloads images from URLs
- Stores them in a local cache
- Handles conversion between formats
- Manages memory usage
Persistent Storage
Notedeck provides several persistence mechanisms:
AccountStorage- For user accountsTimedSerializer- 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 translation framework. The system is designed for performance and developer experience.
Architecture
The localization system consists of several key components:
- LocalizationManager - Core functionality for managing locales and translations
- LocalizationContext - Thread-safe context for sharing localization across the application
- Fluent Resources - Translation files in
.ftlformat stored inassets/translations/
Key Features
- Efficient Caching: Parsed Fluent resources and formatted strings are cached for performance
- Thread Safety: Uses
RwLockfor 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
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
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
-
Always Include Comments: Comments provide context for translators
// Good tr!("Add", "Button label to add something") // Bad tr!("Add", "") -
Use Descriptive Comments: Make comments specific and helpful
// Good tr!("Reply", "Button to reply to a note") // Bad tr!("Reply", "Reply") -
Consistent Parameter Names: Use consistent parameter names across related strings
// Consistent tr!("Follow {user}", "Follow button", user="alice") tr!("Unfollow {user}", "Unfollow button", user="alice") -
Always use tr_plural! for plural strings: Not all languages follow English pluralization rules
// 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 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:
# 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:
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:
# Run localization tests
cargo test i18n
Troubleshooting
Common Issues
-
Relay Connection Issues
- Check network connectivity
- Verify relay URLs are correct
- Look for relay debug messages
-
Database Errors
- Ensure the database path is writable
- Check for database corruption
- Increase map size if needed
-
Performance Issues
- Monitor the frame history
- Check for large image caches
- Consider reducing the number of active subscriptions
-
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:
- Follow the existing code style
- Add tests for new functionality
- Update documentation as needed
- Keep performance in mind, especially for mobile targets
- For UI changes, test with pseudolocale enabled
- When adding new strings, ensure they are properly localized