afro-locale 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,351 @@
1
+ # Contributing to afro-locale
2
+
3
+ Thank you for your interest in contributing to `@koadit/afro-locale`! We appreciate your help in making this library better for the African developer community.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Code of Conduct](#code-of-conduct)
8
+ - [Getting Started](#getting-started)
9
+ - [Development Setup](#development-setup)
10
+ - [Making Changes](#making-changes)
11
+ - [Testing](#testing)
12
+ - [Submitting Changes](#submitting-changes)
13
+ - [Style Guide](#style-guide)
14
+ - [Commit Message Guidelines](#commit-message-guidelines)
15
+
16
+ ## Code of Conduct
17
+
18
+ We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and constructive in all interactions.
19
+
20
+ ## Getting Started
21
+
22
+ 1. Fork the repository on GitHub
23
+ 2. Clone your fork locally
24
+ 3. Create a new branch for your feature or fix
25
+ 4. Make your changes
26
+ 5. Push your branch to your fork
27
+ 6. Submit a pull request
28
+
29
+ ## Development Setup
30
+
31
+ ### Prerequisites
32
+
33
+ - Node.js 14.0 or higher
34
+ - npm 6.0 or higher (or yarn/pnpm)
35
+
36
+ ### Installation
37
+
38
+ ```bash
39
+ # Clone the repository
40
+ git clone https://github.com/oluokunkabiru/afro-locale.git
41
+ cd afro-locale
42
+
43
+ # Install dependencies
44
+ npm install
45
+
46
+ # Build the project
47
+ npm run build
48
+
49
+ # Run tests
50
+ npm test
51
+ ```
52
+
53
+ ### Available Commands
54
+
55
+ ```bash
56
+ # Build for both CJS and ESM
57
+ npm run build
58
+
59
+ # Build CommonJS only
60
+ npm run build:cjs
61
+
62
+ # Build ESM only
63
+ npm run build:esm
64
+
65
+ # Run tests
66
+ npm test
67
+
68
+ # Run tests with coverage
69
+ npm run test:coverage
70
+
71
+ # Run linting
72
+ npm run lint
73
+
74
+ # Type check
75
+ npm run type-check
76
+ ```
77
+
78
+ ## Making Changes
79
+
80
+ ### Branch Naming Convention
81
+
82
+ Use descriptive branch names:
83
+ - `feature/add-new-language` - for new features
84
+ - `fix/currency-formatting` - for bug fixes
85
+ - `docs/update-readme` - for documentation updates
86
+ - `test/improve-coverage` - for test improvements
87
+
88
+ ### File Structure
89
+
90
+ ```
91
+ afro-locale/
92
+ ├── src/
93
+ │ ├── index.ts # Main library code
94
+ │ └── index.test.ts # Test suite
95
+ ├── dist/ # Built files (auto-generated)
96
+ ├── jest.config.js # Jest configuration
97
+ ├── tsconfig.json # TypeScript configuration
98
+ ├── .eslintrc.json # ESLint configuration
99
+ ├── package.json # Package metadata
100
+ ├── README.md # Documentation
101
+ ├── CONTRIBUTING.md # This file
102
+ └── CHANGELOG.md # Version history
103
+ ```
104
+
105
+ ## Testing
106
+
107
+ ### Writing Tests
108
+
109
+ Tests are located in `src/index.test.ts`. When adding new features or fixing bugs, please include corresponding tests.
110
+
111
+ ```typescript
112
+ describe('Feature Name', () => {
113
+ it('should behave correctly', () => {
114
+ // Arrange
115
+ const input = ...;
116
+
117
+ // Act
118
+ const result = ...;
119
+
120
+ // Assert
121
+ expect(result).toBe(...);
122
+ });
123
+ });
124
+ ```
125
+
126
+ ### Running Tests
127
+
128
+ ```bash
129
+ # Run all tests
130
+ npm test
131
+
132
+ # Run tests in watch mode
133
+ npm test -- --watch
134
+
135
+ # Run tests with coverage
136
+ npm run test:coverage
137
+
138
+ # Run specific test file
139
+ npm test -- src/index.test.ts
140
+ ```
141
+
142
+ ### Coverage Requirements
143
+
144
+ - Aim for at least 80% code coverage
145
+ - All new features should have tests
146
+ - Bug fixes should include regression tests
147
+
148
+ ## Submitting Changes
149
+
150
+ ### Before You Submit
151
+
152
+ 1. Ensure all tests pass: `npm test`
153
+ 2. Run linting: `npm run lint`
154
+ 3. Run type checking: `npm run type-check`
155
+ 4. Update the README if necessary
156
+ 5. Add an entry to CHANGELOG.md
157
+
158
+ ### Creating a Pull Request
159
+
160
+ 1. Push your branch to your fork
161
+ 2. Navigate to the original repository
162
+ 3. Click "New Pull Request"
163
+ 4. Fill in the PR template with:
164
+ - Clear title describing the change
165
+ - Description of what changed and why
166
+ - Related issue numbers (if applicable)
167
+ - Checklist of changes
168
+
169
+ ### PR Template
170
+
171
+ ```markdown
172
+ ## Description
173
+ Brief description of the changes
174
+
175
+ ## Related Issues
176
+ Closes #123
177
+
178
+ ## Type of Change
179
+ - [ ] Bug fix
180
+ - [ ] New feature
181
+ - [ ] Documentation update
182
+ - [ ] Performance improvement
183
+
184
+ ## Testing
185
+ - [ ] Added tests for new functionality
186
+ - [ ] All tests pass
187
+ - [ ] Code covered by tests
188
+
189
+ ## Checklist
190
+ - [ ] My code follows the style guidelines
191
+ - [ ] I have updated documentation
192
+ - [ ] I have added tests for my changes
193
+ - [ ] My changes generate no new warnings
194
+ - [ ] I have updated the CHANGELOG.md
195
+ ```
196
+
197
+ ## Style Guide
198
+
199
+ ### TypeScript
200
+
201
+ - Use TypeScript strict mode
202
+ - Add JSDoc comments for public APIs
203
+ - Use meaningful variable and function names
204
+ - Keep functions small and focused
205
+ - Prefer interfaces over type aliases for public APIs
206
+
207
+ ```typescript
208
+ /**
209
+ * Format a number according to locale conventions
210
+ * @param value - The number to format
211
+ * @param options - Optional formatting options
212
+ * @returns Formatted number string
213
+ */
214
+ public formatNumber(value: number, options?: Intl.NumberFormatOptions): string {
215
+ // Implementation
216
+ }
217
+ ```
218
+
219
+ ### Code Quality
220
+
221
+ - Use `const` by default, `let` if reassignment is needed
222
+ - Avoid `var`
223
+ - Avoid `any` types - prefer specific types or generics
224
+ - Keep lines under 100 characters when possible
225
+ - Use meaningful comments for complex logic
226
+
227
+ ### Imports
228
+
229
+ ```typescript
230
+ // Group imports logically
231
+ import type { TypeA, TypeB } from './types';
232
+ import { functionA, functionB } from './utils';
233
+ ```
234
+
235
+ ## Commit Message Guidelines
236
+
237
+ Follow the Conventional Commits specification:
238
+
239
+ ```
240
+ <type>(<scope>): <subject>
241
+
242
+ <body>
243
+
244
+ <footer>
245
+ ```
246
+
247
+ ### Type
248
+
249
+ - `feat`: A new feature
250
+ - `fix`: A bug fix
251
+ - `docs`: Documentation only changes
252
+ - `style`: Changes that don't affect code meaning (formatting, semicolons, etc.)
253
+ - `refactor`: Code change that neither fixes a bug nor adds a feature
254
+ - `perf`: Code change that improves performance
255
+ - `test`: Adding missing tests or correcting existing tests
256
+ - `chore`: Changes to build process, dependencies, etc.
257
+
258
+ ### Scope
259
+
260
+ Optional - the section of the codebase affected (e.g., `currency`, `locale`, `formatting`)
261
+
262
+ ### Subject
263
+
264
+ - Use imperative, present tense: "add" not "added" or "adds"
265
+ - Don't capitalize first letter
266
+ - No period (.) at the end
267
+ - Limit to 50 characters
268
+
269
+ ### Body
270
+
271
+ - Explain what and why, not how
272
+ - Wrap at 72 characters
273
+ - Separate from subject with a blank line
274
+
275
+ ### Footer
276
+
277
+ - Reference issues: `Closes #123`, `Fixes #456`
278
+ - Note breaking changes: `BREAKING CHANGE: description`
279
+
280
+ ### Examples
281
+
282
+ ```
283
+ feat(currency): add support for XOF currency
284
+
285
+ Add West African CFA franc (XOF) to supported currencies.
286
+ Includes proper formatting with correct decimal places.
287
+
288
+ Closes #42
289
+ ```
290
+
291
+ ```
292
+ fix(locale): fix locale normalization for en-US
293
+
294
+ Previously, setting locale to en-US was not properly
295
+ normalized to en. This is now fixed.
296
+
297
+ Fixes #38
298
+ ```
299
+
300
+ ## Adding Support for New Languages
301
+
302
+ To add support for a new language:
303
+
304
+ 1. Update the `languages` map in `src/index.ts`
305
+ 2. Add test cases in `src/index.test.ts`
306
+ 3. Update the README.md with the new language
307
+ 4. Update CHANGELOG.md
308
+ 5. Submit a PR with these changes
309
+
310
+ Example:
311
+
312
+ ```typescript
313
+ private languages: Record<string, LocaleConfig> = {
314
+ // ... existing languages
315
+ xx: { code: 'xx', name: 'Language Name', nativeName: 'Native Name', country: 'XX' },
316
+ };
317
+ ```
318
+
319
+ ## Adding Support for New Currencies
320
+
321
+ To add support for a new currency:
322
+
323
+ 1. Update the `currencyFormats` map in `src/index.ts`
324
+ 2. Add test cases in `src/index.test.ts`
325
+ 3. Update the README.md with the new currency
326
+ 4. Update CHANGELOG.md
327
+ 5. Submit a PR with these changes
328
+
329
+ Example:
330
+
331
+ ```typescript
332
+ private currencyFormats: Record<string, CurrencyFormat> = {
333
+ // ... existing currencies
334
+ XXX: { symbol: 'ₓ', position: 'left', decimals: 2 },
335
+ };
336
+ ```
337
+
338
+ ## Questions?
339
+
340
+ - Open an issue on GitHub for bugs and features
341
+ - Start a discussion for questions and ideas
342
+ - Email: support@koadit.com
343
+
344
+ ## Recognition
345
+
346
+ Contributors will be recognized in:
347
+ - CHANGELOG.md
348
+ - GitHub contributors page
349
+ - Project documentation
350
+
351
+ Thank you for contributing to make afro-locale better! 🚀
package/EXAMPLES.md ADDED
@@ -0,0 +1,327 @@
1
+ # Examples
2
+
3
+ This directory contains practical examples of using `@koadit/afro-locale`.
4
+
5
+ ## Basic Usage
6
+
7
+ ### CommonJS Example
8
+
9
+ ```javascript
10
+ const { locale } = require('@koadit/afro-locale');
11
+
12
+ // Set locale
13
+ locale.set('yo'); // Yoruba
14
+
15
+ // Format currency
16
+ console.log(locale.formatCurrency(5000, 'NGN'));
17
+ // Output: ₦5,000.00
18
+
19
+ // Format date
20
+ console.log(locale.formatDate(new Date(), 'long'));
21
+ // Output: 9 Ȁpẹ̀lẹ̀ 2026
22
+
23
+ // Get language info
24
+ console.log(locale.getLanguageName('yo')); // "Yoruba"
25
+ console.log(locale.getLanguageNativeName('yo')); // "Yorùbá"
26
+ ```
27
+
28
+ ### ES Module Example
29
+
30
+ ```javascript
31
+ import { locale } from '@koadit/afro-locale';
32
+
33
+ locale.set('sw');
34
+ console.log(locale.formatCurrency(1000, 'KES'));
35
+ // Output: KSh1,000.00
36
+ ```
37
+
38
+ ## E-Commerce Example
39
+
40
+ ```javascript
41
+ import { locale } from '@koadit/afro-locale';
42
+
43
+ class ProductFormatter {
44
+ constructor(userLanguage, userCountry) {
45
+ this.userLanguage = userLanguage;
46
+ this.userCountry = userCountry;
47
+ locale.set(userLanguage);
48
+ }
49
+
50
+ formatPrice(price, currency) {
51
+ return locale.formatCurrency(price, currency);
52
+ }
53
+
54
+ formatAvailableDate(date) {
55
+ return locale.formatDate(date, 'long');
56
+ }
57
+
58
+ getLanguageDisplay() {
59
+ const name = locale.getLanguageName(this.userLanguage);
60
+ const native = locale.getLanguageNativeName(this.userLanguage);
61
+ return `${name} (${native})`;
62
+ }
63
+ }
64
+
65
+ // Usage
66
+ const formatter = new ProductFormatter('yo', 'NG');
67
+ console.log(formatter.formatPrice(5000, 'NGN')); // ₦5,000.00
68
+ console.log(formatter.getLanguageDisplay()); // Yoruba (Yorùbá)
69
+ ```
70
+
71
+ ## Multi-Language Support
72
+
73
+ ```javascript
74
+ import { locale } from '@koadit/afro-locale';
75
+
76
+ const priceInUSD = 50;
77
+ const countries = {
78
+ 'Nigeria': { lang: 'yo', currency: 'NGN', rate: 412 },
79
+ 'Kenya': { lang: 'sw', currency: 'KES', rate: 139 },
80
+ 'South Africa': { lang: 'zu', currency: 'ZAR', rate: 18.5 },
81
+ };
82
+
83
+ console.log('Prices in different markets:');
84
+ for (const [country, { lang, currency, rate }] of Object.entries(countries)) {
85
+ locale.set(lang);
86
+ const localPrice = priceInUSD * rate;
87
+ const formatted = locale.formatCurrency(localPrice, currency);
88
+ const langName = locale.getLanguageNativeName(lang);
89
+ console.log(`${country} (${langName}): ${formatted}`);
90
+ }
91
+ ```
92
+
93
+ ## React Example
94
+
95
+ ```jsx
96
+ import React, { useState, useMemo } from 'react';
97
+ import { locale } from '@koadit/afro-locale';
98
+
99
+ function LanguageSelector() {
100
+ const [currentLang, setCurrentLang] = useState('en');
101
+ const languages = useMemo(() => locale.getSupportedLanguages(), []);
102
+
103
+ const handleLanguageChange = (event) => {
104
+ const newLang = event.target.value;
105
+ locale.set(newLang);
106
+ setCurrentLang(newLang);
107
+ };
108
+
109
+ return (
110
+ <select value={currentLang} onChange={handleLanguageChange}>
111
+ {languages.map(lang => (
112
+ <option key={lang.code} value={lang.code}>
113
+ {lang.nativeName}
114
+ </option>
115
+ ))}
116
+ </select>
117
+ );
118
+ }
119
+
120
+ function PriceDisplay({ amount, currency }) {
121
+ const formatted = useMemo(
122
+ () => locale.formatCurrency(amount, currency),
123
+ [amount, currency]
124
+ );
125
+
126
+ return <span>{formatted}</span>;
127
+ }
128
+
129
+ export default function App() {
130
+ return (
131
+ <div>
132
+ <LanguageSelector />
133
+ <PriceDisplay amount={5000} currency="NGN" />
134
+ </div>
135
+ );
136
+ }
137
+ ```
138
+
139
+ ## Vue Example
140
+
141
+ ```vue
142
+ <template>
143
+ <div>
144
+ <select v-model="currentLang" @change="setLanguage">
145
+ <option v-for="lang in languages" :key="lang.code" :value="lang.code">
146
+ {{ lang.nativeName }}
147
+ </option>
148
+ </select>
149
+ <p>Price: {{ formatPrice(5000, 'NGN') }}</p>
150
+ </div>
151
+ </template>
152
+
153
+ <script>
154
+ import { ref, computed } from 'vue';
155
+ import { locale } from '@koadit/afro-locale';
156
+
157
+ export default {
158
+ name: 'LanguageExample',
159
+ setup() {
160
+ const currentLang = ref('en');
161
+ const languages = computed(() => locale.getSupportedLanguages());
162
+
163
+ const setLanguage = (lang) => {
164
+ locale.set(lang);
165
+ };
166
+
167
+ const formatPrice = (amount, currency) => {
168
+ return locale.formatCurrency(amount, currency);
169
+ };
170
+
171
+ return {
172
+ currentLang,
173
+ languages,
174
+ setLanguage,
175
+ formatPrice,
176
+ };
177
+ },
178
+ };
179
+ </script>
180
+ ```
181
+
182
+ ## TypeScript Example
183
+
184
+ ```typescript
185
+ import AfroLocale, { locale, LocaleConfig } from '@koadit/afro-locale';
186
+
187
+ class LocalizationService {
188
+ private locale: AfroLocale;
189
+
190
+ constructor() {
191
+ this.locale = new AfroLocale();
192
+ }
193
+
194
+ setLanguage(code: string): boolean {
195
+ if (this.locale.isLocaleSupported(code)) {
196
+ this.locale.set(code);
197
+ return true;
198
+ }
199
+ return false;
200
+ }
201
+
202
+ formatPrice(amount: number, currency: string): string {
203
+ return this.locale.formatCurrency(amount, currency);
204
+ }
205
+
206
+ formatEventDate(date: Date): string {
207
+ return this.locale.formatDateTime(date, 'long');
208
+ }
209
+
210
+ getAvailableLanguages(): LocaleConfig[] {
211
+ return this.locale.getSupportedLanguages();
212
+ }
213
+ }
214
+
215
+ const service = new LocalizationService();
216
+ service.setLanguage('sw');
217
+ console.log(service.formatPrice(1000, 'KES')); // KSh1,000.00
218
+ ```
219
+
220
+ ## Form Localization Example
221
+
222
+ ```html
223
+ <!DOCTYPE html>
224
+ <html>
225
+ <head>
226
+ <title>Form Localization Example</title>
227
+ </head>
228
+ <body>
229
+ <form id="pricing-form">
230
+ <label>
231
+ Language:
232
+ <select id="language">
233
+ <option value="en">English</option>
234
+ <option value="yo">Yoruba</option>
235
+ <option value="sw">Swahili</option>
236
+ </select>
237
+ </label>
238
+
239
+ <label>
240
+ Amount:
241
+ <input type="number" id="amount" value="5000" />
242
+ </label>
243
+
244
+ <label>
245
+ Currency:
246
+ <select id="currency">
247
+ <option value="NGN">NGN</option>
248
+ <option value="KES">KES</option>
249
+ <option value="ZAR">ZAR</option>
250
+ </select>
251
+ </label>
252
+
253
+ <output id="result"></output>
254
+ </form>
255
+
256
+ <script type="module">
257
+ import { locale } from '@koadit/afro-locale';
258
+
259
+ const form = document.getElementById('pricing-form');
260
+ const languageSelect = document.getElementById('language');
261
+ const amountInput = document.getElementById('amount');
262
+ const currencySelect = document.getElementById('currency');
263
+ const result = document.getElementById('result');
264
+
265
+ function updateResult() {
266
+ const lang = languageSelect.value;
267
+ const amount = parseInt(amountInput.value) || 0;
268
+ const currency = currencySelect.value;
269
+
270
+ locale.set(lang);
271
+ const formatted = locale.formatCurrency(amount, currency);
272
+ result.textContent = formatted;
273
+ }
274
+
275
+ form.addEventListener('change', updateResult);
276
+ form.addEventListener('input', updateResult);
277
+
278
+ updateResult();
279
+ </script>
280
+ </body>
281
+ </html>
282
+ ```
283
+
284
+ ## API Integration Example
285
+
286
+ ```javascript
287
+ import { locale } from '@koadit/afro-locale';
288
+
289
+ class APIClient {
290
+ constructor(userLanguage = 'en') {
291
+ locale.set(userLanguage);
292
+ this.userLanguage = userLanguage;
293
+ }
294
+
295
+ async getProductPrice(productId) {
296
+ const response = await fetch(`/api/products/${productId}`);
297
+ const data = await response.json();
298
+
299
+ // Format price based on user locale
300
+ const currency = this.getCurrencyForLocale();
301
+ const formatted = locale.formatCurrency(data.price, currency);
302
+
303
+ return {
304
+ ...data,
305
+ formattedPrice: formatted,
306
+ };
307
+ }
308
+
309
+ getCurrencyForLocale() {
310
+ const currencyMap = {
311
+ 'yo': 'NGN', // Yoruba
312
+ 'sw': 'KES', // Swahili
313
+ 'zu': 'ZAR', // Zulu
314
+ };
315
+ return currencyMap[this.userLanguage] || 'USD';
316
+ }
317
+ }
318
+
319
+ // Usage
320
+ const client = new APIClient('yo');
321
+ const product = await client.getProductPrice(123);
322
+ console.log(product.formattedPrice);
323
+ ```
324
+
325
+ ---
326
+
327
+ For more information, see the [README.md](../README.md) and [API.md](../API.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KOADIT Digital Solutions
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.