You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
18 KiB
18 KiB
TimeSafari Daily Notification Plugin - Accessibility & Localization Guide
Author: Matthew Raymer
Version: 1.0.0
Created: 2025-10-08 06:08:15 UTC
Overview
This document provides comprehensive guidance for accessibility (A11y) and localization (i18n) implementation in the TimeSafari Daily Notification Plugin. The plugin is designed to be accessible to users with disabilities and support multiple languages and regions.
Accessibility (A11y) Implementation
Screen Reader Support
Notification Content
// Accessible notification structure
const notificationOptions = {
title: 'New Community Update', // Screen reader will announce this
body: 'You have 3 new offers and 2 project updates', // Descriptive content
actions: [
{
id: 'view_offers',
title: 'View Offers', // Clear, actionable label
// Screen reader will announce: "View Offers button"
},
{
id: 'view_projects',
title: 'View Projects', // Clear, actionable label
// Screen reader will announce: "View Projects button"
}
]
};
Platform-Specific A11y
Android:
// Android notification channel with accessibility
const channelConfig = {
id: 'timesafari_community_updates',
name: 'TimeSafari Community Updates', // Screen reader friendly
description: 'Notifications about community offers and project updates', // Descriptive
importance: 'default',
enableVibration: true,
enableLights: true,
sound: 'default'
};
iOS:
// iOS notification category with accessibility
const categoryConfig = {
identifier: 'TIMESAFARI_COMMUNITY_UPDATE',
actions: [
{
identifier: 'VIEW_OFFERS',
title: 'View Offers', // Clear, actionable
options: ['foreground'] // Opens app for better accessibility
},
{
identifier: 'VIEW_PROJECTS',
title: 'View Projects', // Clear, actionable
options: ['foreground']
}
],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: 'New community updates available' // Screen reader fallback
};
Electron:
// Electron notification with accessibility
const electronNotification = {
title: 'TimeSafari Community Update',
body: 'You have new offers and project updates',
actions: [
{ type: 'button', text: 'View Offers' },
{ type: 'button', text: 'View Projects' }
],
// Electron automatically handles screen reader support
silent: false // Ensure sound for accessibility
};
Accessibility Best Practices
1. Content Guidelines
- Clear Language: Use simple, clear language in notifications
- Descriptive Content: Provide context and actionable information
- Consistent Terminology: Use consistent terms across all notifications
- Avoid Jargon: Minimize technical terms and abbreviations
2. Visual Design
- High Contrast: Ensure text is readable against background
- Font Size: Use readable font sizes (minimum 14pt)
- Color Independence: Don't rely solely on color to convey information
- Focus Indicators: Clear focus indicators for interactive elements
3. Interaction Design
- Large Touch Targets: Minimum 44x44pt touch targets
- Clear Actions: Obvious, actionable button labels
- Error Prevention: Clear error messages and recovery options
- Consistent Navigation: Predictable interaction patterns
Accessibility Testing
Automated Testing
// Accessibility test example
describe('Notification Accessibility', () => {
test('should have accessible notification content', () => {
const notification = {
title: 'Test Notification',
body: 'This is a test notification with accessible content'
};
// Test screen reader compatibility
expect(notification.title).toBeTruthy();
expect(notification.body).toBeTruthy();
expect(notification.title.length).toBeLessThan(50); // Optimal length
});
test('should have accessible action buttons', () => {
const actions = [
{ id: 'action1', title: 'Clear Action' },
{ id: 'action2', title: 'Another Action' }
];
actions.forEach(action => {
expect(action.title).toBeTruthy();
expect(action.title.length).toBeGreaterThan(3);
expect(action.title.length).toBeLessThan(30);
});
});
});
Manual Testing Checklist
- Screen Reader: Test with VoiceOver (iOS), TalkBack (Android)
- High Contrast: Test with high contrast mode enabled
- Font Scaling: Test with large text sizes
- Keyboard Navigation: Test with keyboard-only navigation
- Voice Control: Test with voice control features
Localization (i18n) Implementation
Internationalization Keys
Core Notification Strings
// English (en) - Base language
export const i18nKeys = {
// Notification titles
'notification.community_update.title': 'Community Update',
'notification.new_offers.title': 'New Offers Available',
'notification.project_updates.title': 'Project Updates',
'notification.reminder.title': 'Daily Reminder',
// Notification bodies
'notification.community_update.body': 'You have {count} new community updates',
'notification.new_offers.body': '{count} new offers are available for you',
'notification.project_updates.body': 'Your starred projects have {count} updates',
'notification.reminder.body': 'Time to check your TimeSafari community',
// Action buttons
'action.view_offers': 'View Offers',
'action.view_projects': 'View Projects',
'action.view_community': 'View Community',
'action.dismiss': 'Dismiss',
'action.snooze': 'Snooze',
// Error messages
'error.permission_denied': 'Notification permission is required',
'error.network_unavailable': 'Network connection required',
'error.storage_full': 'Storage space is full',
// Status messages
'status.notifications_enabled': 'Notifications are enabled',
'status.notifications_disabled': 'Notifications are disabled',
'status.fetching_content': 'Fetching latest content...',
'status.content_ready': 'Content is ready'
};
Filipino (fil) - Localization
// Filipino (fil) - Localized strings
export const i18nKeysFil = {
// Notification titles
'notification.community_update.title': 'Update ng Komunidad',
'notification.new_offers.title': 'Mga Bagong Alok',
'notification.project_updates.title': 'Mga Update ng Proyekto',
'notification.reminder.title': 'Paalala sa Araw-araw',
// Notification bodies
'notification.community_update.body': 'Mayroon kang {count} na bagong update ng komunidad',
'notification.new_offers.body': 'Mayroon kang {count} na bagong alok',
'notification.project_updates.body': 'Ang iyong mga naka-star na proyekto ay may {count} na update',
'notification.reminder.body': 'Oras na para tingnan ang iyong TimeSafari komunidad',
// Action buttons
'action.view_offers': 'Tingnan ang mga Alok',
'action.view_projects': 'Tingnan ang mga Proyekto',
'action.view_community': 'Tingnan ang Komunidad',
'action.dismiss': 'Itago',
'action.snooze': 'Ipagpaliban',
// Error messages
'error.permission_denied': 'Kailangan ang pahintulot para sa notification',
'error.network_unavailable': 'Kailangan ng koneksyon sa internet',
'error.storage_full': 'Puno na ang storage',
// Status messages
'status.notifications_enabled': 'Naka-enable ang mga notification',
'status.notifications_disabled': 'Naka-disable ang mga notification',
'status.fetching_content': 'Kumukuha ng pinakabagong content...',
'status.content_ready': 'Handa na ang content'
};
Localization Implementation
1. Localization Service
// Localization service implementation
export class LocalizationService {
private currentLocale = 'en';
private translations: Record<string, Record<string, string>> = {
en: i18nKeys,
fil: i18nKeysFil
};
/**
* Set the current locale
*/
setLocale(locale: string): void {
this.currentLocale = locale;
}
/**
* Get localized string
*/
t(key: string, params?: Record<string, string | number>): string {
const translation = this.translations[this.currentLocale]?.[key] ||
this.translations['en'][key] ||
key;
// Replace parameters
if (params) {
return translation.replace(/\{(\w+)\}/g, (match, param) => {
return params[param]?.toString() || match;
});
}
return translation;
}
/**
* Get available locales
*/
getAvailableLocales(): string[] {
return Object.keys(this.translations);
}
}
2. Notification Localization
// Localized notification creation
export class LocalizedNotificationService {
private localizationService = new LocalizationService();
/**
* Create localized notification
*/
async createLocalizedNotification(
type: 'community_update' | 'new_offers' | 'project_updates' | 'reminder',
params: Record<string, string | number> = {},
locale = 'en'
): Promise<void> {
this.localizationService.setLocale(locale);
const title = this.localizationService.t(`notification.${type}.title`);
const body = this.localizationService.t(`notification.${type}.body`, params);
await DailyNotification.scheduleDailyNotification({
title,
body,
time: '09:00',
channel: 'timesafari_community_updates'
});
}
/**
* Create localized notification with actions
*/
async createLocalizedNotificationWithActions(
type: string,
params: Record<string, string | number> = {},
actions: string[] = ['view_offers', 'view_projects'],
locale = 'en'
): Promise<void> {
this.localizationService.setLocale(locale);
const title = this.localizationService.t(`notification.${type}.title`);
const body = this.localizationService.t(`notification.${type}.body`, params);
const localizedActions = actions.map(actionId => ({
id: actionId,
title: this.localizationService.t(`action.${actionId}`)
}));
await DailyNotification.scheduleDailyNotification({
title,
body,
time: '09:00',
channel: 'timesafari_community_updates',
actions: localizedActions
});
}
}
Regional Considerations
1. Date and Time Formatting
// Regional date/time formatting
export class RegionalFormatter {
/**
* Format time for notification scheduling
*/
formatTime(hour: number, minute: number, locale: string): string {
const date = new Date();
date.setHours(hour, minute, 0, 0);
return date.toLocaleTimeString(locale, {
hour: '2-digit',
minute: '2-digit',
hour12: locale === 'en-US' // 12-hour format for US English
});
}
/**
* Format date for notification content
*/
formatDate(date: Date, locale: string): string {
return date.toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
}
2. Number Formatting
// Regional number formatting
export class NumberFormatter {
/**
* Format numbers for notification content
*/
formatNumber(value: number, locale: string): string {
return value.toLocaleString(locale);
}
/**
* Format percentages
*/
formatPercentage(value: number, locale: string): string {
return value.toLocaleString(locale, {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 1
});
}
}
Testing Localization
1. Automated Testing
// Localization test suite
describe('Localization', () => {
const localizationService = new LocalizationService();
test('should support English locale', () => {
localizationService.setLocale('en');
expect(localizationService.t('notification.community_update.title'))
.toBe('Community Update');
});
test('should support Filipino locale', () => {
localizationService.setLocale('fil');
expect(localizationService.t('notification.community_update.title'))
.toBe('Update ng Komunidad');
});
test('should fallback to English for missing translations', () => {
localizationService.setLocale('fil');
expect(localizationService.t('notification.nonexistent.title'))
.toBe('notification.nonexistent.title'); // Falls back to key
});
test('should replace parameters correctly', () => {
localizationService.setLocale('en');
expect(localizationService.t('notification.community_update.body', { count: 5 }))
.toBe('You have 5 new community updates');
});
});
2. Manual Testing Checklist
- English (en): Test all notification strings
- Filipino (fil): Test all notification strings
- Parameter Replacement: Test dynamic content insertion
- Fallback Behavior: Test missing translation handling
- Date/Time Formatting: Test regional date/time formats
- Number Formatting: Test regional number formats
Accessibility and Localization Integration
1. Combined Implementation
// Accessibility-aware localized notifications
export class AccessibleLocalizedNotificationService {
private localizationService = new LocalizationService();
/**
* Create accessible, localized notification
*/
async createAccessibleLocalizedNotification(
type: string,
params: Record<string, string | number> = {},
locale = 'en',
accessibilityOptions: {
highContrast?: boolean;
largeText?: boolean;
screenReader?: boolean;
} = {}
): Promise<void> {
this.localizationService.setLocale(locale);
let title = this.localizationService.t(`notification.${type}.title`);
let body = this.localizationService.t(`notification.${type}.body`, params);
// Apply accessibility enhancements
if (accessibilityOptions.screenReader) {
// Add screen reader specific content
body = `${body} ${this.localizationService.t('accessibility.screen_reader_hint')}`;
}
if (accessibilityOptions.largeText) {
// Ensure content is concise for large text display
if (title.length > 30) {
title = title.substring(0, 27) + '...';
}
if (body.length > 100) {
body = body.substring(0, 97) + '...';
}
}
await DailyNotification.scheduleDailyNotification({
title,
body,
time: '09:00',
channel: 'timesafari_community_updates'
});
}
}
2. User Preference Integration
// User preference integration
export class UserPreferenceService {
/**
* Get user accessibility preferences
*/
async getUserAccessibilityPreferences(): Promise<{
locale: string;
highContrast: boolean;
largeText: boolean;
screenReader: boolean;
reducedMotion: boolean;
}> {
// Get from user settings or system preferences
return {
locale: await this.getSystemLocale(),
highContrast: await this.getHighContrastPreference(),
largeText: await this.getLargeTextPreference(),
screenReader: await this.getScreenReaderPreference(),
reducedMotion: await this.getReducedMotionPreference()
};
}
/**
* Apply user preferences to notifications
*/
async applyUserPreferences(notificationOptions: any): Promise<any> {
const preferences = await this.getUserAccessibilityPreferences();
// Apply localization
const localizationService = new LocalizationService();
localizationService.setLocale(preferences.locale);
// Apply accessibility options
if (preferences.screenReader) {
notificationOptions.accessibilityLabel =
localizationService.t('accessibility.notification_label');
}
if (preferences.largeText) {
notificationOptions.largeText = true;
}
return notificationOptions;
}
}
Compliance and Standards
WCAG 2.1 Compliance
- Level AA: Meets WCAG 2.1 Level AA standards
- Keyboard Navigation: Full keyboard accessibility
- Screen Reader: Compatible with major screen readers
- Color Contrast: Meets contrast ratio requirements
- Text Scaling: Supports up to 200% text scaling
Platform-Specific Guidelines
- Android: Follows Material Design accessibility guidelines
- iOS: Follows Human Interface Guidelines accessibility
- Electron: Follows web accessibility best practices
Internationalization Standards
- Unicode: Full Unicode support for all languages
- RTL Support: Right-to-left language support (future)
- Cultural Adaptation: Regional date/time/number formats
- Fallback Strategy: Graceful fallback to English
Implementation Checklist
Accessibility Implementation
- Screen Reader Support: Test with VoiceOver/TalkBack
- Keyboard Navigation: Full keyboard accessibility
- High Contrast: Test with high contrast modes
- Text Scaling: Test with large text sizes
- Color Independence: Don't rely solely on color
- Focus Indicators: Clear focus indicators
- Error Messages: Accessible error communication
- Loading States: Accessible loading indicators
Localization Implementation
- English (en): Complete translation set
- Filipino (fil): Complete translation set
- Parameter Replacement: Dynamic content insertion
- Fallback Strategy: English fallback for missing translations
- Date/Time Formatting: Regional date/time formats
- Number Formatting: Regional number formats
- Cultural Adaptation: Regional preferences
- Testing: Automated and manual testing
Integration Testing
- Combined A11y + i18n: Test accessibility with localization
- User Preferences: Test user preference integration
- Platform Compatibility: Test across all platforms
- Performance: Test performance impact
- Error Handling: Test error scenarios
- Edge Cases: Test edge cases and boundaries
Note: This guide should be regularly updated as new accessibility and localization requirements are identified. Regular testing and user feedback are essential for maintaining high-quality accessibility and localization support.