Skip to content

Latest commit

 

History

History
745 lines (593 loc) · 15.8 KB

File metadata and controls

745 lines (593 loc) · 15.8 KB

Developer Usage Guide - Ethio-Intll

Table of Contents

Overview

Ethio-Intl is a comprehensive TypeScript library for Ethiopian web applications, providing:

  • Amharic Transliteration: Real-time English-to-Amharic conversion
  • Ethiopian Calendar: Gregorian ↔ Ethiopian date conversion
  • Geez Numerals: Arabic to Geez numeral conversion
  • Multi-language i18n: Support for Amharic, English, Tigrinya, Oromo
  • React Integration: Hooks and components for seamless React apps

Architecture

ethio-intl/
├── localization/     # i18n system (EthioProvider, useEthioIntl)
├── transliteration/  # Amharic conversion (SmartInput, mapping)
├── utils/           # Ethiopian utilities (calendar, numerals)
└── hooks/           # React hooks (useTransliterate)

Installation

NPM

npm install ethio-intl

Yarn

yarn add ethio-intl

PNPM

pnpm add ethio-intl

Peer Dependencies

Ethio-Intl requires React 16.8+ for React features:

npm install react@^16.8.0 react-dom@^16.8.0

TypeScript Support

Full TypeScript support is included - no additional types package needed.

Project Setup

Basic React Setup

import React from 'react';
import ReactDOM from 'react-dom/client';
import { EthioProvider } from 'ethio-intl';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <EthioProvider>
      <App />
    </EthioProvider>
  </React.StrictMode>
);

Advanced Provider Configuration

{% raw %}

import { EthioProvider } from 'ethio-intl';

function App() {
  return (
    <EthioProvider
      defaultLanguage="am"
      supportedLanguages={['en', 'am', 'ti', 'om']}
      resources={{
        en: { common: { greeting: 'Hello' } },
        am: { common: { greeting: 'ሰላም' } }
      }}
      enableHotReload={process.env.NODE_ENV === 'development'}
    >
      <YourApp />
    </EthioProvider>
  );
}

{% endraw %}

Non-React Setup (Vanilla JS)

import { toEthDate, toEthNumber, amharicMap } from 'ethio-intl';

// Use utility functions directly
const ethiopianDate = toEthDate(new Date());
const geezNumber = toEthNumber(2025);

Core Features

1. Amharic Transliteration

SmartInput Component

import { SmartInput } from 'ethio-intl';

function AmharicForm() {
  const [text, setText] = useState({ latin: '', amharic: '' });

  return (
    <SmartInput
      value={text.latin}
      onChange={(latin, amharic) => {
        setText({ latin, amharic });
        console.log('Converted:', amharic);
      }}
      placeholder="Type in English..."
      className="amharic-input"
    />
  );
}

useTransliterate Hook

import { useTransliterate } from 'ethio-intl';

function CustomTransliterator() {
  const { transliterate, isTransliterating } = useTransliterate();

  const handleInput = (latinText) => {
    const amharicText = transliterate(latinText);
    // Do something with amharicText
  };

  return (
    <input
      onChange={(e) => handleInput(e.target.value)}
      placeholder="Type here..."
    />
  );
}

Manual Transliteration

import { amharicMap, reverseMap } from 'ethio-intl';

// Direct character mapping
const transliterateChar = (char) => amharicMap[char.toLowerCase()]?.base || char;

// Batch transliteration
const transliterateText = (text) => {
  return text.split('').map(transliterateChar).join('');
};

2. Ethiopian Calendar

Date Conversion

import { toEthDate, getEthiopianDate, isEthiopianLeapYear } from 'ethio-intl';

// Gregorian to Ethiopian
const gregorianDate = new Date('2024-01-01');
const ethiopianDate = toEthDate(gregorianDate); // "Tikimt 23, 2016"

// Ethiopian date details
const ethDate = getEthiopianDate(2016, 4, 23); // Tikimt 23
console.log(ethDate.day, ethDate.month, ethDate.year);

// Leap year check
const isLeap = isEthiopianLeapYear(2016); // true

Calendar Utilities

import {
  getEthiopianMonthDays,
  AMHARIC_MONTHS,
  getEthiopianMonths
} from 'ethio-intl';

// Get days in a month
const daysInTir = getEthiopianMonthDays(2016, 1); // 30

// Get month names
const months = AMHARIC_MONTHS; // ['Meskerem', 'Tikimt', ...]

// Localized months
const localizedMonths = getEthiopianMonths('am'); // Amharic names

3. Geez Numerals

Number Conversion

import {
  toEthiopianNumerals,
  fromEthiopianNumerals,
  toEthNumber,
  fromEthNumber,
  formatEthNumber
} from 'ethio-intl';

// Arabic to Geez numerals
const geez = toEthiopianNumerals(2025); // "፳፻፳፭"
const arabic = fromEthiopianNumerals('፳፻፳፭'); // 2025

// Alternative format
const formatted = formatEthNumber(2025); // "፳፻፳፭"
const geezNum = toEthNumber(2025); // ፳፻፳፭
const backToArabic = fromEthNumber('፳፻፳፭'); // 2025

4. Internationalization (i18n)

useEthioIntl Hook

import { useEthioIntl } from 'ethio-intl';

function MultilingualComponent() {
  const {
    t,
    currentLang,
    changeLanguage,
    supportedLangs
  } = useEthioIntl();

  return (
    <div>
      <select
        value={currentLang}
        onChange={(e) => changeLanguage(e.target.value)}
      >
        {supportedLangs.map(lang => (
          <option key={lang} value={lang}>{lang.toUpperCase()}</option>
        ))}
      </select>

      <h1>{t('welcome.message')}</h1>
      <p>{t('app.description')}</p>
    </div>
  );
}

Namespaced Translations

function AdvancedTranslations() {
  const { tNamespace } = useEthioIntl();

  // Access namespaced translations
  const userGreeting = tNamespace('user', 'greeting');
  const adminMessage = tNamespace('admin', 'welcome');

  return (
    <div>
      <p>{userGreeting}</p>
      <p>{adminMessage}</p>
    </div>
  );
}

Dynamic Language Loading

function DynamicLoader() {
  const { loadTranslations, loadNamespace } = useEthioIntl();

  const loadFrench = async () => {
    const frenchTranslations = await fetch('/locales/fr.json');
    loadTranslations('fr', frenchTranslations);
  };

  const loadUserModule = async () => {
    const userTranslations = await fetch('/locales/user/fr.json');
    loadNamespace('fr', 'user', userTranslations);
  };

  return (
    <button onClick={loadFrench}>Load French</button>
  );
}

API Reference

Components

SmartInput

interface SmartInputProps {
  value?: string;
  onChange?: (latin: string, amharic: string) => void;
  placeholder?: string;
  className?: string;
  disabled?: boolean;
  autoFocus?: boolean;
}

EthioProvider

interface EthioProviderProps {
  children: ReactNode;
  defaultLanguage?: string;
  supportedLanguages?: string[];
  resources?: Record<string, any>;
  enableHotReload?: boolean;
}

Hooks

useEthioIntl

interface EthioIntlHookResult {
  // Translation functions
  t: (key: string, options?: any) => string;
  tNamespace: (namespace: string, key: string, options?: any) => string;

  // Language management
  currentLang: string;
  changeLanguage: (lang: string) => void;
  supportedLangs: string[];
  detectLanguage: () => string;
  isLanguageSupported: (lang: string) => boolean;

  // Enterprise features
  loadTranslations: (lang: string, translations: Record<string, any>) => void;
  loadNamespace: (lang: string, namespace: string, translations: Record<string, any>) => void;
  unloadNamespace: (lang: string, namespace: string) => void;
  preloadLanguages: (langs: string[]) => Promise<void>;
  getMissingKeys: (lang?: string) => string[];
  exportTranslations: (lang: string) => Record<string, any>;

  // Development
  isDevelopment: boolean;
  enableHotReload: (callback: (lang: string, translations: Record<string, any>) => void) => void;
}

useTransliterate

interface TransliterateHookResult {
  transliterate: (text: string) => string;
  isTransliterating: boolean;
}

Utility Functions

Calendar Functions

toEthDate(date: Date): string
getEthiopianDate(year: number, month: number, day: number): EthiopianDate
isEthiopianLeapYear(year: number): boolean
getEthiopianMonthDays(year: number, month: number): number
AMHARIC_MONTHS: string[]
getEthiopianMonths(lang?: string): string[]

Numeral Functions

toEthiopianNumerals(num: number): string
fromEthiopianNumerals(ethNum: string): number
toEthNumber(num: number): string
fromEthNumber(ethNum: string): number
formatEthNumber(num: number): string

Advanced Usage Patterns

Form Integration

import { SmartInput } from 'ethio-intl';

function EthiopianForm() {
  const [form, setForm] = useState({
    firstName: { latin: '', amharic: '' },
    lastName: { latin: '', amharic: '' },
    address: { latin: '', amharic: '' }
  });

  const updateField = (field) => (latin, amharic) => {
    setForm(prev => ({
      ...prev,
      [field]: { latin, amharic }
    }));
  };

  const handleSubmit = () => {
    // Submit both Latin and Amharic versions
    console.log('Form data:', form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <SmartInput
        value={form.firstName.latin}
        onChange={updateField('firstName')}
        placeholder="First Name"
      />
      <SmartInput
        value={form.lastName.latin}
        onChange={updateField('lastName')}
        placeholder="Last Name"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Multilingual Dashboard

function Dashboard() {
  const { t, currentLang, changeLanguage, supportedLangs } = useEthioIntl();

  return (
    <div className="dashboard">
      <header>
        <h1>{t('dashboard.title')}</h1>
        <select
          value={currentLang}
          onChange={(e) => changeLanguage(e.target.value)}
        >
          {supportedLangs.map(lang => (
            <option key={lang} value={lang}>
              {t(`languages.${lang}`)}
            </option>
          ))}
        </select>
      </header>

      <main>
        <div className="stat-card">
          <h3>{t('stats.users')}</h3>
          <p className="number">{toEthNumber(1234)}</p>
        </div>

        <div className="calendar-widget">
          <h3>{t('calendar.today')}</h3>
          <p>{toEthDate(new Date())}</p>
        </div>
      </main>
    </div>
  );
}

Custom Transliteration Component

function CustomAmharicEditor() {
  const [content, setContent] = useState({
    source: '',
    preview: '',
    wordCount: 0
  });

  const handleTransliteration = (latinText) => {
    // Custom transliteration logic
    const amharicText = transliterate(latinText);

    setContent({
      source: latinText,
      preview: amharicText,
      wordCount: latinText.split(' ').length
    });
  };

  return (
    <div className="editor">
      <div className="input-section">
        <textarea
          value={content.source}
          onChange={(e) => handleTransliteration(e.target.value)}
          placeholder="Type in English..."
          rows={10}
        />
      </div>

      <div className="preview-section">
        <h3>Amharic Preview</h3>
        <div className="preview-content">
          {content.preview || 'Start typing to see Amharic text...'}
        </div>
        <div className="stats">
          Words: {content.wordCount}
        </div>
      </div>
    </div>
  );
}

Best Practices

Performance Optimization

// Memoize expensive operations
const MemoizedTransliterator = React.memo(function Transliterator({ text }) {
  const amharicText = useMemo(() => transliterate(text), [text]);

  return <div>{amharicText}</div>;
});

// Debounce frequent updates
function DebouncedInput() {
  const [value, setValue] = useState('');
  const debouncedTransliterate = useMemo(
    () => debounce((text) => {
      const result = transliterate(text);
      // Update UI
    }, 300),
    []
  );

  return (
    <input
      onChange={(e) => {
        setValue(e.target.value);
        debouncedTransliterate(e.target.value);
      }}
    />
  );
}

Error Handling

function SafeTransliterator() {
  const [result, setResult] = useState('');
  const [error, setError] = useState(null);

  const handleTransliterate = async (text) => {
    try {
      const transliterated = await transliterateAsync(text);
      setResult(transliterated);
      setError(null);
    } catch (err) {
      setError('Transliteration failed');
      console.error('Transliteration error:', err);
    }
  };

  return (
    <div>
      <input onChange={(e) => handleTransliterate(e.target.value)} />
      {error && <div className="error">{error}</div>}
      <div className="result">{result}</div>
    </div>
  );
}

Accessibility

function AccessibleAmharicInput() {
  return (
    <div>
      <label htmlFor="amharic-input">
        Enter text in English (will be converted to Amharic)
      </label>

      <SmartInput
        id="amharic-input"
        aria-describedby="amharic-help"
        placeholder="Type in English..."
      />

      <div id="amharic-help">
        This input automatically converts English text to Amharic script
      </div>

      {/* Screen reader only preview */}
      <div aria-live="polite" className="sr-only">
        Amharic conversion: {amharicText}
      </div>
    </div>
  );
}

Troubleshooting

Common Issues

1. Amharic Characters Not Displaying

Problem: Amharic text appears as boxes or question marks

Solutions:

// Check font loading
import 'ethio-intl/dist/styles/fonts.css';

// Or add to your CSS
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Ethiopic:wght@100..900&display=swap');

.ethiopic-text {
  font-family: 'Noto Sans Ethiopic', 'Nyala', serif;
}

2. Transliteration Not Working

Problem: Input doesn't convert to Amharic

Solutions:

// Verify import
import { SmartInput } from 'ethio-intl'; // Correct
// NOT: import SmartInput from 'ethio-intl/SmartInput'; // Wrong

// Check React version
console.log('React version:', React.version); // Should be 16.8+

3. TypeScript Errors

Problem: Type errors with Ethio-Intl types

Solutions:

// tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "jsx": "react-jsx"
  }
}

Debug Mode

function DebugEthioIntl() {
  const intl = useEthioIntl();

  return (
    <div className="debug-panel">
      <h3>Debug Info</h3>
      <pre>
        {JSON.stringify({
          currentLang: intl.currentLang,
          supportedLangs: intl.supportedLangs,
          isDevelopment: intl.isDevelopment
        }, null, 2)}
      </pre>
    </div>
  );
}

Migration Guide

From v0.x to v1.0.0

Breaking Changes

  1. Provider API Changes:
// Old
<EthioProvider languages={['en', 'am']}>

// New
<EthioProvider supportedLanguages={['en', 'am']}>
  1. Hook Return Values:
// Old
const { translate } = useEthioIntl();

// New
const { t } = useEthioIntl();
  1. Utility Function Renames:
// Old
import { toEthiopian } from 'ethio-intl';

// New
import { toEthDate } from 'ethio-intl';

Migration Steps

  1. Update imports
  2. Replace deprecated function names
  3. Update provider props
  4. Test transliteration behavior
  5. Verify calendar conversions

Support


Happy coding with Ethio-Intl! 🇪🇹