kotori 0.0.0 → 0.0.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Acid Coder
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.
package/README.md CHANGED
@@ -1,7 +1,214 @@
1
- # kotori
1
+ # Kotori
2
2
 
3
- Strongly-typed and composable internationalization library for React
4
- no codegen
5
- no json
3
+ Strongly-typed, modular i18n for React. Variables are inferred directly from your strings — no codegen, no JSON, no schema files.
6
4
 
7
- building...
5
+ ```ts
6
+ const intro = dict({ en: 'Hello {{name}}', zh: '你好 {{name}}' })
7
+
8
+ t('intro', { name: 'John' }) // ✅ typed
9
+ t('intro') // ❌ compile error: missing { name }
10
+ t('intro', { nama: 'John' }) // ❌ compile error: unknown key 'nama'
11
+ ```
12
+
13
+ - No codegen
14
+ - No JSON
15
+ - No dependencies
16
+ - 0.31kb gzipped
17
+ - Modular and tree-shakeable
18
+ - Language change in one page rerenders all pages
19
+ - Variables typed and inferred from string literals
20
+ - Translation keys are typed — no more string typos
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm i kotori
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ **utils.ts**
31
+
32
+ ```ts
33
+ import { kotori } from './kotori'
34
+
35
+ export const { createTranslations, dict } = kotori({
36
+ primaryLanguageTag: 'en',
37
+ secondaryLanguageTags: ['zh', 'ja', 'ms'],
38
+ })
39
+ ```
40
+
41
+ **page1.tsx**
42
+
43
+ ```tsx
44
+ import { createTranslations, dict } from './utils'
45
+
46
+ const intro = dict({
47
+ en: 'my name is {{name}}, I am {{age}} years old.',
48
+ zh: '我叫{{name}},我今年{{age}}岁了。',
49
+ ja: '私の名前は{{name}}で、{{age}}歳です。',
50
+ ms: 'nama saya {{name}}, saya berumur {{age}} tahun.',
51
+ })
52
+
53
+ const time = dict({
54
+ en: 'time {{time}}',
55
+ zh: '时间 {{time}}',
56
+ ja: '時間 {{time}}',
57
+ ms: 'waktu {{time}}',
58
+ // type your arguments, by default it's `Record<string, string>`
59
+ })<{ time: `${number}:${number}:${number}` }>
60
+
61
+ const { useTranslations } = createTranslations({
62
+ intro,
63
+ time,
64
+ })
65
+
66
+
67
+ export const Page1 = () => {
68
+ const { t, setLanguage } = useTranslations()
69
+ return (
70
+ <>
71
+ <select
72
+ name="language"
73
+ onChange={(e) => setLanguage(e.target.value as 'en')}
74
+ >
75
+ <option value="en">English</option>
76
+ <option value="zh">Chinese</option>
77
+ <option value="ja">Japanese</option>
78
+ <option value="ms">Malay</option>
79
+ </select>
80
+ <p>{t('intro', { name: 'John', age: 30 })}</p>
81
+ <p>{t('time', { time: '12:00:00' })}</p>
82
+ </>
83
+ )
84
+ }
85
+ ```
86
+
87
+ **page2.tsx**
88
+
89
+ ```tsx
90
+ import { createTranslations, dict } from './utils'
91
+
92
+ const weather = dict({
93
+ en: 'The weather in {{city}} has {{humidity}}% humidity.',
94
+ zh: '{{city}}的天气湿度为{{humidity}}%。',
95
+ ja: '{{city}}の湿度は{{humidity}}%です。',
96
+ ms: 'Cuaca di {{city}} mempunyai kelembapan {{humidity}}%.',
97
+ })<{ city: string; humidity: number }>
98
+
99
+ const score = dict({
100
+ en: 'Your score is {{score}} out of {{total}}.',
101
+ zh: '你的得分是 {{total}} 分中的 {{score}} 分。',
102
+ ja: 'あなたのスコアは {{total}} 点中 {{score}} 点です。',
103
+ ms: 'Markah anda ialah {{score}} daripada {{total}}.',
104
+ })<{ score: number; total: number }>
105
+
106
+ const lastLogin = dict({
107
+ en: 'Last login: {{date}} at {{time}}',
108
+ zh: '上次登录:{{date}} {{time}}',
109
+ ja: '最終ログイン:{{date}} {{time}}',
110
+ ms: 'Log masuk terakhir: {{date}} pada {{time}}',
111
+ })<{ date: `${number}-${number}-${number}`; time: `${number}:${number}` }>
112
+
113
+ const { useTranslations } = createTranslations({
114
+ greeting,
115
+ score,
116
+ lastLogin,
117
+ })
118
+
119
+ export const Page2 = () => {
120
+ const { t, setLanguage } = useTranslations()
121
+ return (
122
+ <>
123
+ <select
124
+ name="language"
125
+ onChange={(e) => setLanguage(e.target.value as 'en')}
126
+ >
127
+ <option value="en">English</option>
128
+ <option value="zh">Chinese</option>
129
+ <option value="ja">Japanese</option>
130
+ <option value="ms">Malay</option>
131
+ </select>
132
+ <p>{t('weather', { city: 'Kuala Lumpur', humidity: 80 })}</p>
133
+ <p>{t('score', { score: 87, total: 100 })}</p>
134
+ <p>{t('lastLogin', { date: '2024-04-24', time: '09:30' })}</p>
135
+ </>
136
+ )
137
+ }
138
+ ```
139
+
140
+ ## How It Works
141
+
142
+ ### One `kotori` instance per app
143
+
144
+ `kotori` holds the language state. All `createTranslations` calls share that state — changing the language anywhere rerenders everywhere.
145
+
146
+ ### One `createTranslations` per page/feature
147
+
148
+ Translations are colocated with the component that uses them. Bundlers naturally code-split them, so each page only loads what it needs.
149
+
150
+ ### Variables are inferred from string literals
151
+
152
+ kotori parses `{{variable}}` at the type level. No separate type definitions needed — the string *is* the schema.
153
+
154
+ ```ts
155
+ // primary string drives the contract
156
+ const greeting = dict({ en: 'Hi {{name}}', zh: '你好 {{name}}' })
157
+ // ^^^^^^ — inferred as required arg
158
+
159
+ // secondary strings are validated against it
160
+ const mismatch = dict({ en: 'Hi {{name}}', zh: '你好 {{other}}' })
161
+ // ^^^^^^^ — compile error
162
+ ```
163
+
164
+ ### Custom argument types
165
+
166
+ By default, variables are typed as `string`. Pass a generic to narrow them:
167
+
168
+ ```ts
169
+ const time = dict({ en: '{{hour}}:{{minute}}' })<{
170
+ hour: number
171
+ minute: number
172
+ }>
173
+ ```
174
+
175
+ ## API
176
+
177
+ ### `kotori(options)`
178
+
179
+ Creates a scoped i18n instance.
180
+
181
+ | option | type | description |
182
+ |---|---|---|
183
+ | `primaryLanguageTag` | `AllTags` | The source language. Drives variable inference. |
184
+ | `secondaryLanguageTags` | `AllTags[]` | Additional supported languages. |
185
+
186
+ Returns `{ dict, createTranslations }`.
187
+
188
+ ### `dict(translations)(argsType?)`
189
+
190
+ Defines a translation unit. Takes one string per language. Optionally takes a generic to type the interpolated variables.
191
+
192
+ ### `createTranslations(dicts)`
193
+
194
+ Registers a set of dicts and returns `{ useTranslations }`. Call once per page or feature module.
195
+
196
+ ### `useTranslations()`
197
+
198
+ React hook. Returns `{ t, getLanguage, setLanguage }`.
199
+
200
+ | return | description |
201
+ |---|---|
202
+ | `t(key, args?)` | Returns the translated string for the current language. `args` is required if the string has variables, omitted if it doesn't. |
203
+ | `getLanguage()` | Returns the current language tag. |
204
+ | `setLanguage(tag)` | Updates the language and rerenders all active `useTranslations` consumers. |
205
+
206
+ ## Language Tags
207
+
208
+ kotori uses [BCP 47](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) language tags. Both subtags (`en`, `zh`) and full tags (`en-US`, `zh-Hans`) are accepted and validated at the type level.
209
+
210
+ ## Trivial
211
+
212
+ There are already a lot of i18n libraries, and the good names are mostly taken. The original plan was *kotoba* (言葉), the Japanese word for "words" — also taken. Claude suggested *kotori* as an alternative, and it stuck.
213
+
214
+ *Kotori* (小鳥) means "small bird" in Japanese. No deeper relevance to the library — it just sounds nice.
package/dist/index.cjs CHANGED
@@ -1,9 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/isEven.ts
3
- const isEven = (v) => !(v & 1);
4
- //#endregion
5
- //#region src/isOdd.ts
6
- const isOdd = (v) => !isEven(v);
7
- //#endregion
8
- exports.isEven = isEven;
9
- exports.isOdd = isOdd;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`);const t=t=>{let n=new Set,r=t.primaryLanguageTag,i=new Map;return{dict:e=>()=>({translation:e}),createTranslations:t=>{let a=Symbol();return i.set(a,{getLanguage:()=>r,setLanguage:e=>{r=e,i.forEach((e,t)=>{i.set(t,{...e})}),n.forEach(e=>{e()})},t:(e,...n)=>{let i=t[e]?.().translation[r];if(i){for(let e in n[0])i=i.replace(RegExp(`\\{\\{\\s*${e}\\s*\\}\\}`,`g`),()=>String(n[0]?.[e]));return i}}}),{useTranslations:()=>(0,e.useSyncExternalStore)(e=>(n.add(e),()=>n.delete(e)),()=>i.get(a))}}}};exports.kotori=t;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,31 @@
1
- //#region src/isOdd.d.ts
2
- declare const isOdd: (v: number) => boolean;
1
+ //#region node_modules/bcp47-language-tags/dist/zh.d.ts
2
+ type BCP47LanguageTagName = "zh-CN" | "zh-TW" | "zh-HK" | "zh-MO" | "zh-SG" | "zh-CHS" | "zh-CHT" | "en-US" | "en-GB" | "en-CA" | "en-AU" | "en-IN" | "en-ZA" | "en-NZ" | "en-IE" | "en-PH" | "en-ZW" | "en-BZ" | "en-CB" | "en-JM" | "en-TT" | "hi-IN" | "es-ES" | "es-MX" | "es-AR" | "es-CO" | "es-PE" | "es-VE" | "es-CL" | "es-EC" | "es-GT" | "es-CU" | "es-BO" | "es-DO" | "es-HN" | "es-PY" | "es-SV" | "es-NI" | "es-PR" | "es-UY" | "es-PA" | "es-CR" | "ar-EG" | "ar-SA" | "ar-DZ" | "ar-MA" | "ar-IQ" | "ar-SD" | "ar-YE" | "ar-SY" | "ar-TN" | "ar-LY" | "ar-JO" | "ar-LB" | "ar-KW" | "ar-AE" | "ar-BH" | "ar-QA" | "ar-OM" | "pt-BR" | "pt-PT" | "ru-RU" | "ja-JP" | "de-DE" | "de-AT" | "de-CH" | "fr-FR" | "fr-CA" | "fr-BE" | "fr-CH" | "fr-LU" | "fr-MC" | "ko-KR" | "it-IT" | "it-CH" | "tr-TR" | "th-TH" | "el-GR" | "cs-CZ" | "sv-SE" | "sv-FI" | "hu-HU" | "fi-FI" | "da-DK" | "nb-NO" | "nn-NO" | "he-IL" | "id-ID" | "ms-MY" | "ms-BN" | "ro-RO" | "bg-BG" | "uk-UA" | "sk-SK" | "sl-SI" | "hr-HR" | "ca-ES" | "lt-LT" | "lv-LV" | "et-EE" | "sq-AL" | "mk-MK" | "be-BY" | "is-IS" | "gl-ES" | "eu-ES" | "af-ZA" | "sw-KE" | "ta-IN" | "te-IN" | "kn-IN" | "mr-IN" | "gu-IN" | "pa-IN" | "kok-IN" | "sa-IN" | "ur-PK" | "fa-IR" | "syr-SY" | "div-MV" | "ka-GE";
3
3
  //#endregion
4
- //#region src/isEven.d.ts
5
- declare const isEven: (v: number) => boolean;
4
+ //#region src/kotori.d.ts
5
+ type Tags = BCP47LanguageTagName;
6
+ type SubTags = BCP47LanguageTagName extends `${infer SubTag}-${string}` ? SubTag : never;
7
+ type AllTags = Tags | SubTags;
8
+ type Trim<T extends string> = T extends ` ${infer R}` ? Trim<R> : T extends `${infer L} ` ? Trim<L> : T;
9
+ type ExtractVariables<T extends string> = T extends `${string}{{${infer P}}}${infer Q}` ? Trim<P> | ExtractVariables<Q> : never;
10
+ declare const _args: unique symbol;
11
+ declare const kotori: <const PrimaryTag extends AllTags, const SecondaryTags extends Exclude<AllTags, PrimaryTag>>(props: {
12
+ primaryLanguageTag: PrimaryTag;
13
+ secondaryLanguageTags: SecondaryTags[];
14
+ }) => {
15
+ dict: <const PrimaryString extends string, const SecondaryObject extends { [Key in SecondaryTags]: ExtractVariables<PrimaryString> extends infer PrimaryVariables ? ExtractVariables<SecondaryObject[Key] & string> extends infer SecondaryVariables ? PrimaryVariables[] extends SecondaryVariables[] ? SecondaryVariables[] extends PrimaryVariables[] ? SecondaryObject[Key] : "variables not match!" : "variables not match!!" : never : never }>(translation: { [Key in PrimaryTag]: PrimaryString } & SecondaryObject) => <const ArgsType extends Record<ExtractVariables<PrimaryString>, string | number> = Record<ExtractVariables<PrimaryString>, string>>() => Readonly<{
16
+ translation: typeof translation;
17
+ [_args]?: ArgsType;
18
+ }>;
19
+ createTranslations: <const DictCallbacks extends Record<string, () => Readonly<{
20
+ translation: Record<PrimaryTag | SecondaryTags, string>;
21
+ [_args]?: Record<string, string | number>;
22
+ }>>>(dictCallbacks: DictCallbacks) => {
23
+ useTranslations: () => {
24
+ getLanguage: () => PrimaryTag | SecondaryTags;
25
+ setLanguage: (tag: PrimaryTag | SecondaryTags) => void;
26
+ t: <Key extends keyof DictCallbacks>(key: Key, ...args: keyof NonNullable<ReturnType<DictCallbacks[Key]>[typeof _args]> extends never ? [] : [NonNullable<ReturnType<DictCallbacks[Key]>[typeof _args]>]) => Record<PrimaryTag | SecondaryTags, string>[PrimaryTag | SecondaryTags] | undefined;
27
+ };
28
+ };
29
+ };
6
30
  //#endregion
7
- export { isEven, isOdd };
31
+ export { AllTags, ExtractVariables, SubTags, Tags, kotori };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,31 @@
1
- //#region src/isOdd.d.ts
2
- declare const isOdd: (v: number) => boolean;
1
+ //#region node_modules/bcp47-language-tags/dist/zh.d.ts
2
+ type BCP47LanguageTagName = "zh-CN" | "zh-TW" | "zh-HK" | "zh-MO" | "zh-SG" | "zh-CHS" | "zh-CHT" | "en-US" | "en-GB" | "en-CA" | "en-AU" | "en-IN" | "en-ZA" | "en-NZ" | "en-IE" | "en-PH" | "en-ZW" | "en-BZ" | "en-CB" | "en-JM" | "en-TT" | "hi-IN" | "es-ES" | "es-MX" | "es-AR" | "es-CO" | "es-PE" | "es-VE" | "es-CL" | "es-EC" | "es-GT" | "es-CU" | "es-BO" | "es-DO" | "es-HN" | "es-PY" | "es-SV" | "es-NI" | "es-PR" | "es-UY" | "es-PA" | "es-CR" | "ar-EG" | "ar-SA" | "ar-DZ" | "ar-MA" | "ar-IQ" | "ar-SD" | "ar-YE" | "ar-SY" | "ar-TN" | "ar-LY" | "ar-JO" | "ar-LB" | "ar-KW" | "ar-AE" | "ar-BH" | "ar-QA" | "ar-OM" | "pt-BR" | "pt-PT" | "ru-RU" | "ja-JP" | "de-DE" | "de-AT" | "de-CH" | "fr-FR" | "fr-CA" | "fr-BE" | "fr-CH" | "fr-LU" | "fr-MC" | "ko-KR" | "it-IT" | "it-CH" | "tr-TR" | "th-TH" | "el-GR" | "cs-CZ" | "sv-SE" | "sv-FI" | "hu-HU" | "fi-FI" | "da-DK" | "nb-NO" | "nn-NO" | "he-IL" | "id-ID" | "ms-MY" | "ms-BN" | "ro-RO" | "bg-BG" | "uk-UA" | "sk-SK" | "sl-SI" | "hr-HR" | "ca-ES" | "lt-LT" | "lv-LV" | "et-EE" | "sq-AL" | "mk-MK" | "be-BY" | "is-IS" | "gl-ES" | "eu-ES" | "af-ZA" | "sw-KE" | "ta-IN" | "te-IN" | "kn-IN" | "mr-IN" | "gu-IN" | "pa-IN" | "kok-IN" | "sa-IN" | "ur-PK" | "fa-IR" | "syr-SY" | "div-MV" | "ka-GE";
3
3
  //#endregion
4
- //#region src/isEven.d.ts
5
- declare const isEven: (v: number) => boolean;
4
+ //#region src/kotori.d.ts
5
+ type Tags = BCP47LanguageTagName;
6
+ type SubTags = BCP47LanguageTagName extends `${infer SubTag}-${string}` ? SubTag : never;
7
+ type AllTags = Tags | SubTags;
8
+ type Trim<T extends string> = T extends ` ${infer R}` ? Trim<R> : T extends `${infer L} ` ? Trim<L> : T;
9
+ type ExtractVariables<T extends string> = T extends `${string}{{${infer P}}}${infer Q}` ? Trim<P> | ExtractVariables<Q> : never;
10
+ declare const _args: unique symbol;
11
+ declare const kotori: <const PrimaryTag extends AllTags, const SecondaryTags extends Exclude<AllTags, PrimaryTag>>(props: {
12
+ primaryLanguageTag: PrimaryTag;
13
+ secondaryLanguageTags: SecondaryTags[];
14
+ }) => {
15
+ dict: <const PrimaryString extends string, const SecondaryObject extends { [Key in SecondaryTags]: ExtractVariables<PrimaryString> extends infer PrimaryVariables ? ExtractVariables<SecondaryObject[Key] & string> extends infer SecondaryVariables ? PrimaryVariables[] extends SecondaryVariables[] ? SecondaryVariables[] extends PrimaryVariables[] ? SecondaryObject[Key] : "variables not match!" : "variables not match!!" : never : never }>(translation: { [Key in PrimaryTag]: PrimaryString } & SecondaryObject) => <const ArgsType extends Record<ExtractVariables<PrimaryString>, string | number> = Record<ExtractVariables<PrimaryString>, string>>() => Readonly<{
16
+ translation: typeof translation;
17
+ [_args]?: ArgsType;
18
+ }>;
19
+ createTranslations: <const DictCallbacks extends Record<string, () => Readonly<{
20
+ translation: Record<PrimaryTag | SecondaryTags, string>;
21
+ [_args]?: Record<string, string | number>;
22
+ }>>>(dictCallbacks: DictCallbacks) => {
23
+ useTranslations: () => {
24
+ getLanguage: () => PrimaryTag | SecondaryTags;
25
+ setLanguage: (tag: PrimaryTag | SecondaryTags) => void;
26
+ t: <Key extends keyof DictCallbacks>(key: Key, ...args: keyof NonNullable<ReturnType<DictCallbacks[Key]>[typeof _args]> extends never ? [] : [NonNullable<ReturnType<DictCallbacks[Key]>[typeof _args]>]) => Record<PrimaryTag | SecondaryTags, string>[PrimaryTag | SecondaryTags] | undefined;
27
+ };
28
+ };
29
+ };
6
30
  //#endregion
7
- export { isEven, isOdd };
31
+ export { AllTags, ExtractVariables, SubTags, Tags, kotori };
package/dist/index.mjs CHANGED
@@ -1,7 +1 @@
1
- //#region src/isEven.ts
2
- const isEven = (v) => !(v & 1);
3
- //#endregion
4
- //#region src/isOdd.ts
5
- const isOdd = (v) => !isEven(v);
6
- //#endregion
7
- export { isEven, isOdd };
1
+ import{useSyncExternalStore as e}from"react";const t=t=>{let n=new Set,r=t.primaryLanguageTag,i=new Map;return{dict:e=>()=>({translation:e}),createTranslations:t=>{let a=Symbol();return i.set(a,{getLanguage:()=>r,setLanguage:e=>{r=e,i.forEach((e,t)=>{i.set(t,{...e})}),n.forEach(e=>{e()})},t:(e,...n)=>{let i=t[e]?.().translation[r];if(i){for(let e in n[0])i=i.replace(RegExp(`\\{\\{\\s*${e}\\s*\\}\\}`,`g`),()=>String(n[0]?.[e]));return i}}}),{useTranslations:()=>e(e=>(n.add(e),()=>n.delete(e)),()=>i.get(a))}}}};export{t as kotori};
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "kotori",
3
3
  "description": "Strongly-typed and composable internationalization library for React",
4
- "version": "0.0.0",
4
+ "version": "0.0.1",
5
5
  "scripts": {
6
6
  "setup": "rm -rf node_modules && npm i && git init && husky",
7
7
  "prepublishOnly": "npm run build",
8
8
  "build": "tsdown",
9
9
  "test": "vitest",
10
- "lint": "npx @biomejs/biome check --write"
10
+ "lint": "npx @biomejs/biome check --write",
11
+ "dev": "vite --host"
11
12
  },
12
13
  "files": [
13
14
  "dist"
@@ -35,9 +36,15 @@
35
36
  "devDependencies": {
36
37
  "@biomejs/biome": "^2.4.12",
37
38
  "@types/node": "^25.6.0",
39
+ "@types/react": "^19.2.14",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^6.0.1",
38
42
  "@vitest/coverage-v8": "^4.1.5",
43
+ "bcp47-language-tags": "^1.1.0",
39
44
  "husky": "^9.1.7",
40
45
  "lint-staged": "^16.4.0",
46
+ "react": "^19.2.5",
47
+ "react-dom": "^19.2.5",
41
48
  "tsdown": "^0.21.10",
42
49
  "tsx": "^4.21.0",
43
50
  "typescript": "^6.0.3",
@@ -45,11 +52,14 @@
45
52
  },
46
53
  "repository": {
47
54
  "type": "git",
48
- "url": "git+https://github.com/???/???.git"
55
+ "url": "git+https://github.com/tylim88/kotori.git"
49
56
  },
50
57
  "bugs": {
51
- "url": "https://github.com/???/???/issues"
58
+ "url": "https://github.com/tylim88/kotori/issues"
52
59
  },
53
- "author": "???",
54
- "license": "???"
60
+ "author": "tylim88",
61
+ "license": "MIT",
62
+ "peerDependencies": {
63
+ "react": ">=19.2.5"
64
+ }
55
65
  }