@serendie/ui 2.2.7 → 2.2.8-dev.202511270751
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/README.md +105 -1
- package/dist/components/TextField/TextField.js +35 -34
- package/dist/i18n/dictionary.d.ts +10 -0
- package/dist/i18n/dictionary.js +13 -0
- package/dist/i18n/index.d.ts +20 -0
- package/dist/i18n/index.js +34 -0
- package/dist/i18n/provider.d.ts +14 -0
- package/dist/i18n/provider.js +15 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -105,11 +105,48 @@ export default defineConfig({
|
|
|
105
105
|
|
|
106
106
|
より実践的な例は、こちらの[サンプルプロジェクト](https://github.com/serendie/bootcamp?tab=readme-ov-file#%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%A8%E4%BD%B5%E7%94%A8%E3%81%99%E3%82%8B)を参考にしてください。
|
|
107
107
|
|
|
108
|
+
## 多言語対応
|
|
109
|
+
|
|
110
|
+
Serendie UIは日本語・英語の多言語対応をサポートしています。`SerendieProvider`を使用して、アプリケーション全体の言語を設定できます。なお、 SerendieProvider の利用は必須ではありません。利用しない場合は、デフォルトで日本語が適用されます。
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { SerendieProvider } from "@serendie/ui";
|
|
114
|
+
|
|
115
|
+
function App() {
|
|
116
|
+
return (
|
|
117
|
+
<SerendieProvider lang="ja">{/* アプリケーション全体 */}</SerendieProvider>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Next.js App Routerでの多言語対応
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// app/layout.tsx
|
|
126
|
+
import { SerendieProvider } from "@serendie/ui";
|
|
127
|
+
|
|
128
|
+
export default function RootLayout({
|
|
129
|
+
children,
|
|
130
|
+
params,
|
|
131
|
+
}: {
|
|
132
|
+
children: React.ReactNode;
|
|
133
|
+
params: { lang: "ja" | "en" };
|
|
134
|
+
}) {
|
|
135
|
+
return (
|
|
136
|
+
<html lang={params.lang}>
|
|
137
|
+
<body>
|
|
138
|
+
<SerendieProvider lang={params.lang}>{children}</SerendieProvider>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
108
145
|
## APIを詳しく知る
|
|
109
146
|
|
|
110
147
|
Serendie UIはヘッドレスUIとして、[Ark UI](https://ark-ui.com/)を内部的に利用しており、各コンポーネントのAPIはArk UIを継承します。Selectコンポーネントなどインタラクションが複雑なコンポーネントは、Ark UIの[APIリファレンス](https://ark-ui.com/react/docs/components/select#api-reference)を合わせて参照してください。
|
|
111
148
|
|
|
112
|
-
## Serendie UI
|
|
149
|
+
## Serendie UI開発者向け
|
|
113
150
|
|
|
114
151
|
Serendie UIに新しくコンポーネントを追加する場合は、Ark UIをベースにしてください。
|
|
115
152
|
|
|
@@ -128,6 +165,73 @@ npm run connect:publish
|
|
|
128
165
|
|
|
129
166
|
storiesファイルに変更が入ると上記が[GitHub Actions](https://github.com/serendie/serendie/blob/main/.github/workflows/publish-code-connect.yml)によって実行されます。
|
|
130
167
|
|
|
168
|
+
### 翻訳データの管理
|
|
169
|
+
|
|
170
|
+
Serendie UIの翻訳データは`src/i18n/dictionary.ts`で管理されており、Figma Variablesと同期できます。
|
|
171
|
+
|
|
172
|
+
#### コンポーネント内での翻訳の使用
|
|
173
|
+
|
|
174
|
+
`useTranslations`フックを使用して、コンポーネント内で翻訳テキストを取得できます:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useTranslations } from "@serendie/ui";
|
|
178
|
+
|
|
179
|
+
function MyComponent() {
|
|
180
|
+
const t = useTranslations();
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div>
|
|
184
|
+
{/* 変数なし */}
|
|
185
|
+
<label>{t("common.required")}</label>
|
|
186
|
+
|
|
187
|
+
{/* 変数あり - {{key}} プレースホルダーを使用 */}
|
|
188
|
+
<span>{t("pagination.page", { page: 5 })}</span>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
翻訳辞書では`{{key}}`形式のプレースホルダーを使用します:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// src/i18n/dictionary.ts
|
|
198
|
+
export const dictionary = {
|
|
199
|
+
ja: {
|
|
200
|
+
"common.required": "必須",
|
|
201
|
+
"pagination.page": "{{page}}ページ目",
|
|
202
|
+
},
|
|
203
|
+
en: {
|
|
204
|
+
"common.required": "Required",
|
|
205
|
+
"pagination.page": "Page {{page}}",
|
|
206
|
+
},
|
|
207
|
+
} as const;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 環境設定
|
|
211
|
+
|
|
212
|
+
`.env`ファイルに以下を設定してください:
|
|
213
|
+
|
|
214
|
+
```env
|
|
215
|
+
FIGMA_ACCESS_TOKEN="YOUR_TOKEN"
|
|
216
|
+
FIGMA_FILE_KEY="YOUR_FILE_KEY"
|
|
217
|
+
# FIGMA_TRANSLATION_COLLECTION="locales" # オプション(デフォルト: locales)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### 翻訳管理コマンド
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Figmaから翻訳データを取得して src/i18n/dictionary.ts を更新
|
|
224
|
+
npm run locales:pull
|
|
225
|
+
|
|
226
|
+
# ローカルの翻訳データをFigma Variablesに反映
|
|
227
|
+
npm run locales:push
|
|
228
|
+
|
|
229
|
+
# 翻訳データの整合性チェック(キーの不足や空文字のチェック)
|
|
230
|
+
npm run locales:lint
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
翻訳データの詳細については[scripts/locales/README.md](scripts/locales/README.md)を参照してください。
|
|
234
|
+
|
|
131
235
|
## Resources
|
|
132
236
|
|
|
133
237
|
Serendie Design Systemは、Serendie UI (本リポジトリ) のほか以下の関連リポジトリから構成されています。
|
|
@@ -1,81 +1,82 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { SerendieSymbolAlertCircle as
|
|
3
|
-
import
|
|
4
|
-
import m, { forwardRef as
|
|
1
|
+
import { jsxs as a, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { SerendieSymbolAlertCircle as A, SerendieSymbolClose as W } from "@serendie/symbols";
|
|
3
|
+
import z from "../../node_modules/merge-refs/dist/esm/index.js";
|
|
4
|
+
import m, { forwardRef as B } from "react";
|
|
5
5
|
import { textFieldRecipe as x } from "../../recipes/textFieldRecipe.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useTranslations as D } from "../../i18n/index.js";
|
|
7
|
+
import { cx as E } from "../../styled-system/css/cx.js";
|
|
7
8
|
import { css as b } from "../../styled-system/css/css.js";
|
|
8
|
-
const
|
|
9
|
+
const U = B(
|
|
9
10
|
({
|
|
10
11
|
placeholder: F,
|
|
11
12
|
label: d,
|
|
12
13
|
description: i,
|
|
13
14
|
required: u,
|
|
14
15
|
requiredLabel: S,
|
|
15
|
-
invalidMessage:
|
|
16
|
-
invalid:
|
|
16
|
+
invalidMessage: n,
|
|
17
|
+
invalid: r,
|
|
17
18
|
type: V = "text",
|
|
18
|
-
disabled:
|
|
19
|
+
disabled: c,
|
|
19
20
|
onChange: h,
|
|
20
21
|
value: f,
|
|
21
22
|
className: w,
|
|
22
23
|
leftContent: N,
|
|
23
24
|
rightContent: p,
|
|
24
|
-
...
|
|
25
|
+
...s
|
|
25
26
|
}, y) => {
|
|
26
|
-
const o = m.useRef(null),
|
|
27
|
-
...
|
|
28
|
-
}), t = x(
|
|
27
|
+
const C = D(), o = m.useRef(null), P = z(o, y), [_, j] = x.splitVariantProps({
|
|
28
|
+
...s
|
|
29
|
+
}), t = x(_), I = i || r && n, [T, k] = m.useState(s.defaultValue || f), v = s.id || m.useId(), q = () => {
|
|
29
30
|
var R;
|
|
30
|
-
const
|
|
31
|
+
const l = {
|
|
31
32
|
target: { value: "" }
|
|
32
33
|
};
|
|
33
|
-
g(
|
|
34
|
-
}, g = (
|
|
35
|
-
k(
|
|
34
|
+
g(l), (R = s.onReset) == null || R.call(s, l), o.current && (o.current.value = "");
|
|
35
|
+
}, g = (l) => {
|
|
36
|
+
k(l.target.value), h && h(l);
|
|
36
37
|
};
|
|
37
|
-
return /* @__PURE__ */
|
|
38
|
-
d ? /* @__PURE__ */
|
|
38
|
+
return /* @__PURE__ */ a("div", { className: E(t.root, w), children: [
|
|
39
|
+
d ? /* @__PURE__ */ a("label", { className: t.label, htmlFor: v, children: [
|
|
39
40
|
d,
|
|
40
|
-
u && /* @__PURE__ */ e("span", { className: t.labelRequired, children: S ?? "
|
|
41
|
+
u && /* @__PURE__ */ e("span", { className: t.labelRequired, children: S ?? C("common.required") })
|
|
41
42
|
] }) : null,
|
|
42
|
-
/* @__PURE__ */
|
|
43
|
+
/* @__PURE__ */ a(
|
|
43
44
|
"div",
|
|
44
45
|
{
|
|
45
46
|
className: t.inputWrapper,
|
|
46
|
-
"data-invalid":
|
|
47
|
-
"data-disabled":
|
|
47
|
+
"data-invalid": r ? !0 : void 0,
|
|
48
|
+
"data-disabled": c ? !0 : void 0,
|
|
48
49
|
children: [
|
|
49
50
|
N ? /* @__PURE__ */ e("div", { className: t.leftContent, children: N }) : /* @__PURE__ */ e("div", {}),
|
|
50
51
|
/* @__PURE__ */ e(
|
|
51
52
|
"input",
|
|
52
53
|
{
|
|
53
|
-
ref:
|
|
54
|
+
ref: P,
|
|
54
55
|
id: v,
|
|
55
56
|
placeholder: F,
|
|
56
57
|
required: u,
|
|
57
|
-
disabled:
|
|
58
|
+
disabled: c,
|
|
58
59
|
value: f,
|
|
59
60
|
type: V,
|
|
60
61
|
className: t.input,
|
|
61
62
|
onChange: g,
|
|
62
|
-
...
|
|
63
|
+
...j
|
|
63
64
|
}
|
|
64
65
|
),
|
|
65
|
-
p ? /* @__PURE__ */ e("div", { className: t.rightContent, children: p }) : /* @__PURE__ */ e("div", { className: t.icon, children: !
|
|
66
|
-
(
|
|
66
|
+
p ? /* @__PURE__ */ e("div", { className: t.rightContent, children: p }) : /* @__PURE__ */ e("div", { className: t.icon, children: !c && /* disabledの場合はアイコンを表示しない */
|
|
67
|
+
(r ? /* @__PURE__ */ e(
|
|
67
68
|
"span",
|
|
68
69
|
{
|
|
69
70
|
className: b({
|
|
70
71
|
color: "sd.system.color.impression.negative"
|
|
71
72
|
}),
|
|
72
|
-
children: /* @__PURE__ */ e(
|
|
73
|
+
children: /* @__PURE__ */ e(A, { width: 20, height: 20 })
|
|
73
74
|
}
|
|
74
|
-
) :
|
|
75
|
+
) : T ? /* @__PURE__ */ e(
|
|
75
76
|
"button",
|
|
76
77
|
{
|
|
77
78
|
className: b({ cursor: "pointer" }),
|
|
78
|
-
onClick:
|
|
79
|
+
onClick: q,
|
|
79
80
|
"aria-label": "値をクリア",
|
|
80
81
|
children: /* @__PURE__ */ e(W, { width: 20, height: 20 })
|
|
81
82
|
}
|
|
@@ -83,13 +84,13 @@ const M = z(
|
|
|
83
84
|
]
|
|
84
85
|
}
|
|
85
86
|
),
|
|
86
|
-
|
|
87
|
+
I && /* @__PURE__ */ a("div", { className: t.messageField, children: [
|
|
87
88
|
i && /* @__PURE__ */ e("p", { className: t.description, children: i }),
|
|
88
|
-
|
|
89
|
+
r && n && /* @__PURE__ */ e("p", { className: t.invalidMessage, children: n })
|
|
89
90
|
] })
|
|
90
91
|
] });
|
|
91
92
|
}
|
|
92
93
|
);
|
|
93
94
|
export {
|
|
94
|
-
|
|
95
|
+
U as TextField
|
|
95
96
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { dictionary } from './dictionary';
|
|
2
|
+
import { Language } from './provider';
|
|
3
|
+
export { SerendieProvider, LanguageProvider, type Language } from './provider';
|
|
4
|
+
export type TranslationKey = keyof typeof dictionary.ja;
|
|
5
|
+
/**
|
|
6
|
+
* 翻訳関数を生成するヘルパー
|
|
7
|
+
* Context不要な場合や、サーバーサイドで直接使う場合に使用
|
|
8
|
+
*/
|
|
9
|
+
export declare function getTranslations(lang: Language): (key: TranslationKey, params?: Record<string, string | number>) => string;
|
|
10
|
+
/**
|
|
11
|
+
* 翻訳関数を取得するReact Hook
|
|
12
|
+
* SerendieProviderがない場合はデフォルト言語(ja)を使用します
|
|
13
|
+
*/
|
|
14
|
+
export declare function useTranslations(): (key: TranslationKey, params?: Record<string, string | number>) => string;
|
|
15
|
+
/**
|
|
16
|
+
* 言語に合わせて日付文字列をフォーマットする
|
|
17
|
+
* @param date ISO形式などDateが解釈できる文字列
|
|
18
|
+
* @param lang 言語
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatDateByLang(date: string, lang: Language): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useContext as l } from "react";
|
|
2
|
+
import { dictionary as u } from "./dictionary.js";
|
|
3
|
+
import { LanguageContext as s } from "./provider.js";
|
|
4
|
+
import { LanguageProvider as x, SerendieProvider as P } from "./provider.js";
|
|
5
|
+
function c(e) {
|
|
6
|
+
return function(r, t) {
|
|
7
|
+
let n = u[e][r];
|
|
8
|
+
return !n && e !== "ja" && (n = u.ja[r]), n ? (t && Object.entries(t).forEach(([a, f]) => {
|
|
9
|
+
const i = `{{${a}}}`;
|
|
10
|
+
n.includes(i) || console.warn(
|
|
11
|
+
`Placeholder "${i}" not found in translation "${n}"`
|
|
12
|
+
), n = n.replace(
|
|
13
|
+
new RegExp(`\\{\\{${a}\\}\\}`, "g"),
|
|
14
|
+
String(f)
|
|
15
|
+
);
|
|
16
|
+
}), n) : (console.warn(`Translation key "${r}" not found`), r);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function m() {
|
|
20
|
+
const e = l(s);
|
|
21
|
+
return !e && process.env.NODE_ENV === "development" && console.warn(
|
|
22
|
+
"SerendieProvider is not found. Using default language 'ja'. Consider wrapping your app with <SerendieProvider lang='ja'>"
|
|
23
|
+
), c(e || "ja");
|
|
24
|
+
}
|
|
25
|
+
function j(e, o) {
|
|
26
|
+
return new Date(e).toLocaleDateString(o === "en" ? "en-US" : "ja-JP");
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
x as LanguageProvider,
|
|
30
|
+
P as SerendieProvider,
|
|
31
|
+
j as formatDateByLang,
|
|
32
|
+
c as getTranslations,
|
|
33
|
+
m as useTranslations
|
|
34
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Language = "ja" | "en";
|
|
2
|
+
export declare const LanguageContext: import('react').Context<Language | undefined>;
|
|
3
|
+
/**
|
|
4
|
+
* Serendie UIの設定を提供するProvider
|
|
5
|
+
* アプリケーションのルートで使用してください
|
|
6
|
+
*/
|
|
7
|
+
export declare function SerendieProvider({ lang, children, }: {
|
|
8
|
+
lang: Language;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated LanguageProvider は SerendieProvider にリネームされました
|
|
13
|
+
*/
|
|
14
|
+
export declare const LanguageProvider: typeof SerendieProvider;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as o } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as t } from "react";
|
|
3
|
+
const n = t(void 0);
|
|
4
|
+
function i({
|
|
5
|
+
lang: e,
|
|
6
|
+
children: r
|
|
7
|
+
}) {
|
|
8
|
+
return /* @__PURE__ */ o(n.Provider, { value: e, children: r });
|
|
9
|
+
}
|
|
10
|
+
const u = i;
|
|
11
|
+
export {
|
|
12
|
+
n as LanguageContext,
|
|
13
|
+
u as LanguageProvider,
|
|
14
|
+
i as SerendieProvider
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@serendie/ui",
|
|
3
3
|
"description": "Adaptive UI component library as part of Serendie Design System by Mitsubishi Electric",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "2.2.
|
|
5
|
+
"version": "2.2.8-dev.202511270751",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"sideEffects": [
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"connect:publish": "npx figma connect publish --include-template-files",
|
|
25
25
|
"connect:unpublish": "npx figma connect unpublish",
|
|
26
26
|
"chromatic": "chromatic --only-changed --exit-zero-on-changes --build-script-name=build:storybook",
|
|
27
|
-
"release": "release-it --config ./.release-it.json"
|
|
27
|
+
"release": "release-it --config ./.release-it.json",
|
|
28
|
+
"locales:pull": "tsx scripts/locales/pull.ts",
|
|
29
|
+
"locales:push": "tsx scripts/locales/push.ts",
|
|
30
|
+
"locales:lint": "tsx scripts/locales/lint.ts"
|
|
28
31
|
},
|
|
29
32
|
"peerDependencies": {
|
|
30
33
|
"@ark-ui/react": "^5.18.0",
|
|
@@ -81,6 +84,7 @@
|
|
|
81
84
|
"prettier": "^3.3.3",
|
|
82
85
|
"release-it": "^17.11.0",
|
|
83
86
|
"storybook": "^8.2.4",
|
|
87
|
+
"tsx": "^4.20.6",
|
|
84
88
|
"typescript": "^5.5.3",
|
|
85
89
|
"typescript-eslint": "^8.0.0",
|
|
86
90
|
"vite": "^5.3.3",
|