refactor: localesをworkspace管理下のパッケージに (#16895)
* refactor: localesをworkspace管理下のパッケージに * fix copilot review * move * move * rename * fix ci * revert unwanted indent changes * fix * fix * fix * fix * 間違えてコミットしていたのを戻す * 不要 * 追加漏れ * ymlの場所だけ戻す * localesの位置を戻したのでこの差分は不要 * 内容的にlocalesにある方が正しい * i18nパッケージ用のREADME.mdを用意 * fix locale.yml * fix locale.yml --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
parent
32b5583432
commit
fe01a5a28f
|
|
@ -111,10 +111,5 @@ jobs:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- run: pnpm --filter misskey-js run build
|
- run: pnpm --filter "${{ matrix.workspace }}^..." run build
|
||||||
if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'frontend' || matrix.workspace == 'sw' }}
|
|
||||||
- run: pnpm --filter misskey-reversi run build
|
|
||||||
if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'frontend' }}
|
|
||||||
- run: pnpm --filter misskey-bubble-game run build
|
|
||||||
if: ${{ matrix.workspace == 'frontend' }}
|
|
||||||
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
|
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ name: Lint
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
- packages/i18n/**
|
||||||
- locales/**
|
- locales/**
|
||||||
- .github/workflows/locale.yml
|
- .github/workflows/locale.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- packages/i18n/**
|
||||||
- locales/**
|
- locales/**
|
||||||
- .github/workflows/locale.yml
|
- .github/workflows/locale.yml
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -14,15 +16,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.3.0
|
- uses: actions/checkout@v4.3.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4.2.0
|
uses: pnpm/action-setup@v4.2.0
|
||||||
- uses: actions/setup-node@v4.4.0
|
- uses: actions/setup-node@v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: ".node-version"
|
||||||
cache: 'pnpm'
|
cache: "pnpm"
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- run: cd locales && node verify.js
|
- run: pnpm --filter i18n build
|
||||||
|
- name: Verify Locales
|
||||||
|
working-directory: ./packages/i18n
|
||||||
|
run: pnpm run verify
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-share
|
||||||
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
|
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
|
||||||
COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"]
|
COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"]
|
||||||
COPY --link ["packages/frontend-builder/package.json", "./packages/frontend-builder/"]
|
COPY --link ["packages/frontend-builder/package.json", "./packages/frontend-builder/"]
|
||||||
|
COPY --link ["packages/i18n/package.json", "./packages/i18n/"]
|
||||||
COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"]
|
COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"]
|
||||||
COPY --link ["packages/sw/package.json", "./packages/sw/"]
|
COPY --link ["packages/sw/package.json", "./packages/sw/"]
|
||||||
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
|
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
|
||||||
|
|
@ -101,6 +102,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
||||||
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/i18n/built ./packages/i18n/built
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||||
COPY --chown=misskey:misskey . ./
|
COPY --chown=misskey:misskey . ./
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import { dirname } from 'node:path';
|
|
||||||
import * as yaml from 'js-yaml';
|
|
||||||
import ts from 'typescript';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
const parameterRegExp = /\{(\w+)\}/g;
|
|
||||||
|
|
||||||
function createMemberType(item) {
|
|
||||||
if (typeof item !== 'string') {
|
|
||||||
return ts.factory.createTypeLiteralNode(createMembers(item));
|
|
||||||
}
|
|
||||||
const parameters = Array.from(
|
|
||||||
item.matchAll(parameterRegExp),
|
|
||||||
([, parameter]) => parameter,
|
|
||||||
);
|
|
||||||
return parameters.length
|
|
||||||
? ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('ParameterizedString'),
|
|
||||||
[
|
|
||||||
ts.factory.createUnionTypeNode(
|
|
||||||
parameters.map((parameter) =>
|
|
||||||
ts.factory.createStringLiteral(parameter),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMembers(record) {
|
|
||||||
return Object.entries(record).map(([k, v]) => {
|
|
||||||
const node = ts.factory.createPropertySignature(
|
|
||||||
undefined,
|
|
||||||
ts.factory.createStringLiteral(k),
|
|
||||||
undefined,
|
|
||||||
createMemberType(v),
|
|
||||||
);
|
|
||||||
if (typeof v === 'string') {
|
|
||||||
ts.addSyntheticLeadingComment(
|
|
||||||
node,
|
|
||||||
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
||||||
`*
|
|
||||||
* ${v.replace(/\n/g, '\n * ')}
|
|
||||||
`,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function generateDTS() {
|
|
||||||
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
|
||||||
const members = createMembers(locale);
|
|
||||||
const elements = [
|
|
||||||
ts.factory.createVariableStatement(
|
|
||||||
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
|
||||||
ts.factory.createVariableDeclarationList(
|
|
||||||
[
|
|
||||||
ts.factory.createVariableDeclaration(
|
|
||||||
ts.factory.createIdentifier('kParameters'),
|
|
||||||
undefined,
|
|
||||||
ts.factory.createTypeOperatorNode(
|
|
||||||
ts.SyntaxKind.UniqueKeyword,
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ts.NodeFlags.Const,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ts.factory.createTypeAliasDeclaration(
|
|
||||||
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
ts.factory.createIdentifier('ParameterizedString'),
|
|
||||||
[
|
|
||||||
ts.factory.createTypeParameterDeclaration(
|
|
||||||
undefined,
|
|
||||||
ts.factory.createIdentifier('T'),
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ts.factory.createIntersectionTypeNode([
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
||||||
ts.factory.createTypeLiteralNode([
|
|
||||||
ts.factory.createPropertySignature(
|
|
||||||
undefined,
|
|
||||||
ts.factory.createComputedPropertyName(
|
|
||||||
ts.factory.createIdentifier('kParameters'),
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('T'),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
ts.factory.createInterfaceDeclaration(
|
|
||||||
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
ts.factory.createIdentifier('ILocale'),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
[
|
|
||||||
ts.factory.createIndexSignature(
|
|
||||||
undefined,
|
|
||||||
[
|
|
||||||
ts.factory.createParameterDeclaration(
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
ts.factory.createIdentifier('_'),
|
|
||||||
undefined,
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ts.factory.createUnionTypeNode([
|
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
||||||
ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('ParameterizedString'),
|
|
||||||
),
|
|
||||||
ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('ILocale'),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ts.factory.createInterfaceDeclaration(
|
|
||||||
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
ts.factory.createIdentifier('Locale'),
|
|
||||||
undefined,
|
|
||||||
[
|
|
||||||
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
|
||||||
ts.factory.createExpressionWithTypeArguments(
|
|
||||||
ts.factory.createIdentifier('ILocale'),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
members,
|
|
||||||
),
|
|
||||||
ts.factory.createVariableStatement(
|
|
||||||
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
|
||||||
ts.factory.createVariableDeclarationList(
|
|
||||||
[
|
|
||||||
ts.factory.createVariableDeclaration(
|
|
||||||
ts.factory.createIdentifier('locales'),
|
|
||||||
undefined,
|
|
||||||
ts.factory.createTypeLiteralNode([
|
|
||||||
ts.factory.createIndexSignature(
|
|
||||||
undefined,
|
|
||||||
[
|
|
||||||
ts.factory.createParameterDeclaration(
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
ts.factory.createIdentifier('lang'),
|
|
||||||
undefined,
|
|
||||||
ts.factory.createKeywordTypeNode(
|
|
||||||
ts.SyntaxKind.StringKeyword,
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('Locale'),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ts.NodeFlags.Const,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ts.factory.createFunctionDeclaration(
|
|
||||||
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
undefined,
|
|
||||||
ts.factory.createIdentifier('build'),
|
|
||||||
undefined,
|
|
||||||
[],
|
|
||||||
ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('Locale'),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
|
|
||||||
];
|
|
||||||
ts.addSyntheticLeadingComment(
|
|
||||||
elements[0],
|
|
||||||
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
||||||
' eslint-disable ',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
ts.addSyntheticLeadingComment(
|
|
||||||
elements[0],
|
|
||||||
ts.SyntaxKind.SingleLineCommentTrivia,
|
|
||||||
' This file is generated by locales/generateDTS.js',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
ts.addSyntheticLeadingComment(
|
|
||||||
elements[0],
|
|
||||||
ts.SyntaxKind.SingleLineCommentTrivia,
|
|
||||||
' Do not edit this file directly.',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
const printed = ts
|
|
||||||
.createPrinter({
|
|
||||||
newLine: ts.NewLineKind.LineFeed,
|
|
||||||
})
|
|
||||||
.printList(
|
|
||||||
ts.ListFormat.MultiLine,
|
|
||||||
ts.factory.createNodeArray(elements),
|
|
||||||
ts.createSourceFile(
|
|
||||||
'index.d.ts',
|
|
||||||
'',
|
|
||||||
ts.ScriptTarget.ESNext,
|
|
||||||
true,
|
|
||||||
ts.ScriptKind.TS,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
/**
|
|
||||||
* Languages Loader
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as yaml from 'js-yaml';
|
|
||||||
|
|
||||||
const merge = (...args) => args.reduce((a, c) => ({
|
|
||||||
...a,
|
|
||||||
...c,
|
|
||||||
...Object.entries(a)
|
|
||||||
.filter(([k]) => c && typeof c[k] === 'object')
|
|
||||||
.reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {})
|
|
||||||
}), {});
|
|
||||||
|
|
||||||
const languages = [
|
|
||||||
'ar-SA',
|
|
||||||
'ca-ES',
|
|
||||||
'cs-CZ',
|
|
||||||
'da-DK',
|
|
||||||
'de-DE',
|
|
||||||
'en-US',
|
|
||||||
'es-ES',
|
|
||||||
'fr-FR',
|
|
||||||
'id-ID',
|
|
||||||
'it-IT',
|
|
||||||
'ja-JP',
|
|
||||||
'ja-KS',
|
|
||||||
'kab-KAB',
|
|
||||||
'kn-IN',
|
|
||||||
'ko-KR',
|
|
||||||
'nl-NL',
|
|
||||||
'no-NO',
|
|
||||||
'pl-PL',
|
|
||||||
'pt-PT',
|
|
||||||
'ru-RU',
|
|
||||||
'sk-SK',
|
|
||||||
'th-TH',
|
|
||||||
'tr-TR',
|
|
||||||
'ug-CN',
|
|
||||||
'uk-UA',
|
|
||||||
'vi-VN',
|
|
||||||
'zh-CN',
|
|
||||||
'zh-TW',
|
|
||||||
];
|
|
||||||
|
|
||||||
const primaries = {
|
|
||||||
'en': 'US',
|
|
||||||
'ja': 'JP',
|
|
||||||
'zh': 'CN',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
|
|
||||||
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
|
||||||
|
|
||||||
export function build() {
|
|
||||||
// vitestの挙動を調整するため、一度ローカル変数化する必要がある
|
|
||||||
// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
|
|
||||||
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
|
|
||||||
const metaUrl = import.meta.url;
|
|
||||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
|
|
||||||
|
|
||||||
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
|
|
||||||
const removeEmpty = (obj) => {
|
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
|
||||||
if (v === '') {
|
|
||||||
delete obj[k];
|
|
||||||
} else if (typeof v === 'object') {
|
|
||||||
removeEmpty(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
removeEmpty(locales);
|
|
||||||
|
|
||||||
return Object.entries(locales)
|
|
||||||
.reduce((a, [k, v]) => (a[k] = (() => {
|
|
||||||
const [lang] = k.split('-');
|
|
||||||
switch (k) {
|
|
||||||
case 'ja-JP': return v;
|
|
||||||
case 'ja-KS':
|
|
||||||
case 'en-US': return merge(locales['ja-JP'], v);
|
|
||||||
default: return merge(
|
|
||||||
locales['ja-JP'],
|
|
||||||
locales['en-US'],
|
|
||||||
locales[`${lang}-${primaries[lang]}`] ?? {},
|
|
||||||
v
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})(), a), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default build();
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import locales from './index.js';
|
|
||||||
|
|
||||||
let valid = true;
|
|
||||||
|
|
||||||
function writeError(type, lang, tree, data) {
|
|
||||||
process.stderr.write(JSON.stringify({ type, lang, tree, data }));
|
|
||||||
process.stderr.write('\n');
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify(expected, actual, lang, trace) {
|
|
||||||
for (let key in expected) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(actual, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (typeof expected[key] === 'object') {
|
|
||||||
if (typeof actual[key] !== 'object') {
|
|
||||||
writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'object', actual: typeof actual[key] });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
verify(expected[key], actual[key], lang, trace ? `${trace}.${key}` : key);
|
|
||||||
} else if (typeof expected[key] === 'string') {
|
|
||||||
switch (typeof actual[key]) {
|
|
||||||
case 'object':
|
|
||||||
writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'string', actual: 'object' });
|
|
||||||
break;
|
|
||||||
case 'undefined':
|
|
||||||
continue;
|
|
||||||
case 'string':
|
|
||||||
const expectedParameters = new Set(expected[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
|
|
||||||
const actualParameters = new Set(actual[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
|
|
||||||
for (let parameter of expectedParameters) {
|
|
||||||
if (!actualParameters.has(parameter)) {
|
|
||||||
writeError('missing_parameter', lang, trace ? `${trace}.${key}` : key, { parameter });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ['ja-JP']: original, ...verifiees } = locales;
|
|
||||||
|
|
||||||
for (let lang in verifiees) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(locales, lang)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
verify(original, verifiees[lang], lang);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
17
package.json
17
package.json
|
|
@ -8,15 +8,17 @@
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.22.0",
|
"packageManager": "pnpm@10.22.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend-shared",
|
|
||||||
"packages/frontend",
|
|
||||||
"packages/frontend-embed",
|
|
||||||
"packages/icons-subsetter",
|
|
||||||
"packages/backend",
|
|
||||||
"packages/sw",
|
|
||||||
"packages/misskey-js",
|
"packages/misskey-js",
|
||||||
|
"packages/i18n",
|
||||||
"packages/misskey-reversi",
|
"packages/misskey-reversi",
|
||||||
"packages/misskey-bubble-game"
|
"packages/misskey-bubble-game",
|
||||||
|
"packages/icons-subsetter",
|
||||||
|
"packages/frontend-shared",
|
||||||
|
"packages/frontend-builder",
|
||||||
|
"packages/sw",
|
||||||
|
"packages/backend",
|
||||||
|
"packages/frontend",
|
||||||
|
"packages/frontend-embed"
|
||||||
],
|
],
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -68,6 +70,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.39.1",
|
"@eslint/js": "9.39.1",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"@misskey-dev/eslint-plugin": "2.2.0",
|
"@misskey-dev/eslint-plugin": "2.2.0",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
//const locales = await import('../../../../locales/index.js');
|
//const locales = await import('i18n');
|
||||||
|
|
||||||
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
|
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
let untilTime = untilId ? this.toXListId(untilId) : null;
|
let untilTime = untilId ? this.toXListId(untilId) : null;
|
||||||
|
|
||||||
let notifications: MiNotification[];
|
let notifications: MiNotification[];
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
let notificationsRes: [id: string, fields: string[]][];
|
let notificationsRes: [id: string, fields: string[]][];
|
||||||
|
|
||||||
// sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照
|
// sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { collectModifications } from './locale-inliner/collect-modifications.js'
|
||||||
import { applyWithLocale } from './locale-inliner/apply-with-locale.js';
|
import { applyWithLocale } from './locale-inliner/apply-with-locale.js';
|
||||||
import { blankLogger } from './logger.js';
|
import { blankLogger } from './logger.js';
|
||||||
import type { Logger } from './logger.js';
|
import type { Logger } from './logger.js';
|
||||||
import type { Locale } from '../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
import type { Manifest as ViteManifest } from 'vite';
|
import type { Manifest as ViteManifest } from 'vite';
|
||||||
|
|
||||||
export class LocaleInliner {
|
export class LocaleInliner {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import { assertNever } from '../utils.js';
|
import { assertNever } from '../utils.js';
|
||||||
import type { Locale, ILocale } from '../../../locales/index.js';
|
import type { ILocale, Locale } from 'i18n';
|
||||||
import type { TextModification } from '../locale-inliner.js';
|
import type { TextModification } from '../locale-inliner.js';
|
||||||
import type { Logger } from '../logger.js';
|
import type { Logger } from '../logger.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"i18n": "workspace:*",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"magic-string": "0.30.21",
|
"magic-string": "0.30.21",
|
||||||
"vite": "7.2.4"
|
"vite": "7.2.4"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as fs from 'fs/promises';
|
||||||
import url from 'node:url';
|
import url from 'node:url';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import locales from '../../locales/index.js';
|
import locales from 'i18n';
|
||||||
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
|
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
|
||||||
import { createLogger } from '../frontend-builder/logger';
|
import { createLogger } from '../frontend-builder/logger';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.3",
|
"@rollup/plugin-replace": "6.0.3",
|
||||||
"@rollup/pluginutils": "5.3.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends string | ParameterizedString">
|
<script setup lang="ts" generic="T extends string | ParameterizedString">
|
||||||
import { computed, h } from 'vue';
|
import { computed, h } from 'vue';
|
||||||
import type { ParameterizedString } from '../../../../locales/index.js';
|
import type { ParameterizedString } from 'i18n';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src: T;
|
src: T;
|
||||||
|
|
@ -25,7 +25,7 @@ const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: (
|
||||||
const parsed = computed(() => {
|
const parsed = computed(() => {
|
||||||
let str = props.src as string;
|
let str = props.src as string;
|
||||||
const value: (string | { arg: string; })[] = [];
|
const value: (string | { arg: string; })[] = [];
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
const nextBracketOpen = str.indexOf('{');
|
const nextBracketOpen = str.indexOf('{');
|
||||||
const nextBracketClose = str.indexOf('}');
|
const nextBracketClose = str.indexOf('}');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { I18n } from '@@/js/i18n.js';
|
import { I18n } from '@@/js/i18n.js';
|
||||||
import { locale } from '@@/js/locale.js';
|
import { locale } from '@@/js/locale.js';
|
||||||
import type { Locale } from '../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
|
|
||||||
export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
|
export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import pluginVue from '@vitejs/plugin-vue';
|
import pluginVue from '@vitejs/plugin-vue';
|
||||||
import { type UserConfig, defineConfig } from 'vite';
|
import { defineConfig, type UserConfig } from 'vite';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { promises as fsp } from 'fs';
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
import locales from '../../locales/index.js';
|
import locales from 'i18n';
|
||||||
import meta from '../../package.json';
|
import meta from '../../package.json';
|
||||||
import packageInfo from './package.json' with { type: 'json' };
|
import packageInfo from './package.json' with { type: 'json' };
|
||||||
import pluginJson5 from './vite.json5.js';
|
import pluginJson5 from './vite.json5.js';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ILocale, ParameterizedString } from '../../../locales/index.js';
|
import type { ILocale, ParameterizedString } from 'i18n';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type TODO = any;
|
type TODO = any;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { lang, version } from '@@/js/config.js';
|
import { lang, version } from '@@/js/config.js';
|
||||||
import type { Locale } from '../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
|
|
||||||
// ここはビルド時に const locale = JSON.parse("...") みたいな感じで置き換えられるので top-level await は消える
|
// ここはビルド時に const locale = JSON.parse("...") みたいな感じで置き換えられるので top-level await は消える
|
||||||
export let locale: Locale = await window.fetch(`/assets/locales/${lang}.${version}.json`).then(r => r.json(), () => null);
|
export let locale: Locale = await window.fetch(`/assets/locales/${lang}.${version}.json`).then(r => r.json(), () => null);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Locale } from '../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
|
|
||||||
type BootLoaderLocaleBody = Locale['_bootErrors'] & { reload: Locale['reload'] };
|
type BootLoaderLocaleBody = Locale['_bootErrors'] & { reload: Locale['reload'] };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"js-built"
|
"js-built"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"i18n": "workspace:*",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"vue": "3.5.24"
|
"vue": "3.5.24"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { writeFile } from 'node:fs/promises';
|
import { writeFile } from 'node:fs/promises';
|
||||||
import locales from '../../../locales/index.js';
|
import locales from 'i18n';
|
||||||
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
new URL('locale.ts', import.meta.url),
|
new URL('locale.ts', import.meta.url),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as fs from 'fs/promises';
|
||||||
import url from 'node:url';
|
import url from 'node:url';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import locales from '../../locales/index.js';
|
import locales from 'i18n';
|
||||||
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
|
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
|
||||||
import { createLogger } from '../frontend-builder/logger';
|
import { createLogger } from '../frontend-builder/logger';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import locales from '../../../locales/index.js';
|
import locales from 'i18n';
|
||||||
|
|
||||||
const localesDir = path.resolve(__dirname, '../../../locales')
|
const localesDir = path.resolve(__dirname, '../../../locales')
|
||||||
|
|
||||||
|
|
@ -13,14 +13,14 @@ const localesDir = path.resolve(__dirname, '../../../locales')
|
||||||
* @returns {import('vite').Plugin}
|
* @returns {import('vite').Plugin}
|
||||||
*/
|
*/
|
||||||
export default function pluginWatchLocales() {
|
export default function pluginWatchLocales() {
|
||||||
return {
|
return {
|
||||||
name: 'watch-locales',
|
name: 'watch-locales',
|
||||||
|
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
const localeYmlPaths = Object.keys(locales).map(locale => path.join(localesDir, `${locale}.yml`));
|
const localeYmlPaths = Object.keys(locales).map(locale => path.join(localesDir, `${locale}.yml`));
|
||||||
|
|
||||||
// watcherにパスを追加
|
// watcherにパスを追加
|
||||||
server.watcher.add(localeYmlPaths);
|
server.watcher.add(localeYmlPaths);
|
||||||
|
|
||||||
server.watcher.on('change', (filePath) => {
|
server.watcher.on('change', (filePath) => {
|
||||||
if (localeYmlPaths.includes(filePath)) {
|
if (localeYmlPaths.includes(filePath)) {
|
||||||
|
|
@ -31,6 +31,6 @@ export default function pluginWatchLocales() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@github/webauthn-json": "2.1.1",
|
"@github/webauthn-json": "2.1.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.3",
|
"@rollup/plugin-replace": "6.0.3",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends string | ParameterizedString">
|
<script setup lang="ts" generic="T extends string | ParameterizedString">
|
||||||
import { computed, h } from 'vue';
|
import { computed, h } from 'vue';
|
||||||
import type { ParameterizedString } from '../../../../../locales/index.js';
|
import type { ParameterizedString } from 'i18n';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src: T;
|
src: T;
|
||||||
|
|
@ -25,7 +25,7 @@ const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: (
|
||||||
const parsed = computed(() => {
|
const parsed = computed(() => {
|
||||||
let str = props.src as string;
|
let str = props.src as string;
|
||||||
const value: (string | { arg: string; })[] = [];
|
const value: (string | { arg: string; })[] = [];
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
const nextBracketOpen = str.indexOf('{');
|
const nextBracketOpen = str.indexOf('{');
|
||||||
const nextBracketClose = str.indexOf('}');
|
const nextBracketClose = str.indexOf('}');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { I18n } from '@@/js/i18n.js';
|
import { I18n } from '@@/js/i18n.js';
|
||||||
import { locale } from '@@/js/locale.js';
|
import { locale } from '@@/js/locale.js';
|
||||||
import type { Locale } from '../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
|
|
||||||
export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
|
export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { I18n } from '../../frontend-shared/js/i18n.js'; // @@で参照できなかったので
|
import { I18n } from '../../frontend-shared/js/i18n.js'; // @@で参照できなかったので
|
||||||
import type { ParameterizedString } from '../../../locales/index.js';
|
import type { ParameterizedString } from 'i18n';
|
||||||
|
|
||||||
// TODO: このテストはfrontend-sharedに移動する
|
// TODO: このテストはfrontend-sharedに移動する
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ import { vi } from 'vitest';
|
||||||
import createFetchMock from 'vitest-fetch-mock';
|
import createFetchMock from 'vitest-fetch-mock';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
// Set i18n
|
||||||
|
import locales from 'i18n';
|
||||||
|
import { updateI18n } from '@/i18n.js';
|
||||||
|
|
||||||
const fetchMocker = createFetchMock(vi);
|
const fetchMocker = createFetchMock(vi);
|
||||||
fetchMocker.enableMocks();
|
fetchMocker.enableMocks();
|
||||||
|
|
||||||
// Set i18n
|
|
||||||
import locales from '../../../locales/index.js';
|
|
||||||
import { updateI18n } from '@/i18n.js';
|
|
||||||
updateI18n(locales['en-US']);
|
updateI18n(locales['en-US']);
|
||||||
|
|
||||||
// XXX: misskey-js panics if WebSocket is not defined
|
// XXX: misskey-js panics if WebSocket is not defined
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ import path from 'path';
|
||||||
import pluginReplace from '@rollup/plugin-replace';
|
import pluginReplace from '@rollup/plugin-replace';
|
||||||
import pluginVue from '@vitejs/plugin-vue';
|
import pluginVue from '@vitejs/plugin-vue';
|
||||||
import pluginGlsl from 'vite-plugin-glsl';
|
import pluginGlsl from 'vite-plugin-glsl';
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import type { UserConfig } from 'vite';
|
import type { UserConfig } from 'vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { promises as fsp } from 'fs';
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
import locales from '../../locales/index.js';
|
import locales from 'i18n';
|
||||||
import meta from '../../package.json';
|
import meta from '../../package.json';
|
||||||
import packageInfo from './package.json' with { type: 'json' };
|
import packageInfo from './package.json' with { type: 'json' };
|
||||||
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
||||||
import pluginJson5 from './vite.json5.js';
|
import pluginJson5 from './vite.json5.js';
|
||||||
import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js';
|
|
||||||
import type { Options as SearchIndexOptions } from './lib/vite-plugin-create-search-index.js';
|
import type { Options as SearchIndexOptions } from './lib/vite-plugin-create-search-index.js';
|
||||||
|
import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js';
|
||||||
import pluginWatchLocales from './lib/vite-plugin-watch-locales.js';
|
import pluginWatchLocales from './lib/vite-plugin-watch-locales.js';
|
||||||
import { pluginRemoveUnrefI18n } from '../frontend-builder/rollup-plugin-remove-unref-i18n.js';
|
import { pluginRemoveUnrefI18n } from '../frontend-builder/rollup-plugin-remove-unref-i18n.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Misskey i18n
|
||||||
|
|
||||||
|
Misskey の言語ファイル本体 (ja-JP.yml など) はリポジトリ直下の `/locales` に置かれており、そこから Crowdin 連携やビルド資産が生成されます。
|
||||||
|
|
||||||
|
このパッケージは Misskey モノレポ内で、これらの言語ファイルを共通で扱うためのヘルパー群や型情報をまとめる位置づけです。バックエンド / フロントエンド / Service Worker など各パッケージが同じ翻訳データと型定義を利用できるようにすることを目的としており、npm での外部配布は想定していません。
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { watch as chokidarWatch } from 'chokidar';
|
||||||
|
import * as esbuild from 'esbuild';
|
||||||
|
import { build } from 'esbuild';
|
||||||
|
import { execa } from 'execa';
|
||||||
|
import { globSync } from 'glob';
|
||||||
|
import { generateLocaleInterface } from './scripts/generateLocaleInterface.js';
|
||||||
|
import type { BuildOptions, BuildResult, Plugin, PluginBuild } from 'esbuild';
|
||||||
|
|
||||||
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
|
const _dirname = dirname(_filename);
|
||||||
|
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||||
|
const _rootPackageDir = resolve(_dirname, '../../');
|
||||||
|
const _rootPackage = JSON.parse(fs.readFileSync(resolve(_rootPackageDir, 'package.json'), 'utf-8'));
|
||||||
|
const _frontendLocalesDir = resolve(_dirname, '../../built/_frontend_dist_/locales');
|
||||||
|
const _localesDir = resolve(_rootPackageDir, 'locales');
|
||||||
|
|
||||||
|
const entryPoints = globSync('./src/**/**.{ts,tsx}');
|
||||||
|
|
||||||
|
const options: BuildOptions = {
|
||||||
|
entryPoints,
|
||||||
|
minify: process.env.NODE_ENV === 'production',
|
||||||
|
sourceRoot: 'src',
|
||||||
|
outdir: './built',
|
||||||
|
target: 'es2022',
|
||||||
|
platform: 'node',
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: 'linked',
|
||||||
|
};
|
||||||
|
|
||||||
|
// コマンドライン引数を取得
|
||||||
|
const args = process.argv.slice(2).map(arg => arg.toLowerCase());
|
||||||
|
|
||||||
|
// built配下をすべて削除する
|
||||||
|
if (!args.includes('--no-clean')) {
|
||||||
|
fs.rmSync('./built', { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--watch')) {
|
||||||
|
await watchSrc();
|
||||||
|
} else {
|
||||||
|
await buildSrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyLocales(): void {
|
||||||
|
const srcDir = _localesDir;
|
||||||
|
const destDir = resolve(_dirname, 'built/locales');
|
||||||
|
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
|
||||||
|
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.yml'));
|
||||||
|
for (const file of files) {
|
||||||
|
fs.copyFileSync(resolve(srcDir, file), resolve(destDir, file));
|
||||||
|
}
|
||||||
|
console.log(`[${_package.name}] locales copied (${files.length} files).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フロントエンド用の locale JSON を書き出す
|
||||||
|
* Service Worker が HTTP 経由で取得するために必要
|
||||||
|
*/
|
||||||
|
async function writeFrontendLocalesJson(): Promise<void> {
|
||||||
|
// 動的 import でビルド済みモジュールから読み込み(循環参照回避)
|
||||||
|
const { writeFrontendLocalesJson: write } = await import('./built/index.js');
|
||||||
|
await write(_frontendLocalesDir, _rootPackage.version);
|
||||||
|
console.log(`[${_package.name}] frontend locales JSON written to ${_frontendLocalesDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildSrc(): Promise<void> {
|
||||||
|
console.log(`[${_package.name}] start building...`);
|
||||||
|
|
||||||
|
await generateLocaleInterface(_localesDir);
|
||||||
|
|
||||||
|
await build(options)
|
||||||
|
.then(() => {
|
||||||
|
console.log(`[${_package.name}] build succeeded.`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
process.stderr.write(err.stderr);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
copyLocales();
|
||||||
|
await writeFrontendLocalesJson();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
|
||||||
|
} else {
|
||||||
|
await buildDts();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[${_package.name}] finish building.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDts(): Promise<unknown> {
|
||||||
|
return execa(
|
||||||
|
'tsc',
|
||||||
|
[
|
||||||
|
'--project', 'tsconfig.json',
|
||||||
|
'--rootDir', 'src',
|
||||||
|
'--outDir', 'built',
|
||||||
|
'--declaration', 'true',
|
||||||
|
'--emitDeclarationOnly', 'true',
|
||||||
|
],
|
||||||
|
{
|
||||||
|
stdout: process.stdout,
|
||||||
|
stderr: process.stderr,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function watchSrc(): Promise<void> {
|
||||||
|
const localesWatcher = chokidarWatch(_localesDir, {
|
||||||
|
ignoreInitial: true,
|
||||||
|
});
|
||||||
|
localesWatcher.on('all', async (event, path) => {
|
||||||
|
if (!path.endsWith('.yml')) return;
|
||||||
|
console.log(`[${_package.name}] locales changed: ${event} ${path}`);
|
||||||
|
copyLocales();
|
||||||
|
await writeFrontendLocalesJson();
|
||||||
|
await generateLocaleInterface(_localesDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
const plugins: Plugin[] = [{
|
||||||
|
name: 'gen-dts',
|
||||||
|
setup(build: PluginBuild) {
|
||||||
|
build.onStart(() => {
|
||||||
|
console.log(`[${_package.name}] detect changed...`);
|
||||||
|
});
|
||||||
|
build.onEnd(async (result: BuildResult) => {
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
console.error(`[${_package.name}] watch build failed:`, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await buildDts();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
console.log(`[${_package.name}] start watching...`);
|
||||||
|
|
||||||
|
const context = await esbuild.context({ ...options, plugins });
|
||||||
|
await context.watch();
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
process.on('SIGHUP', resolve);
|
||||||
|
process.on('SIGINT', resolve);
|
||||||
|
process.on('SIGTERM', resolve);
|
||||||
|
process.on('uncaughtException', reject);
|
||||||
|
process.on('exit', resolve);
|
||||||
|
}).finally(async () => {
|
||||||
|
await context.dispose();
|
||||||
|
await localesWatcher.close();
|
||||||
|
console.log(`[${_package.name}] finish watching.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import sharedConfig from '../shared/eslint.config.js';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default [
|
||||||
|
...sharedConfig,
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'**/node_modules',
|
||||||
|
'built',
|
||||||
|
'coverage',
|
||||||
|
'vitest.config.ts',
|
||||||
|
'test',
|
||||||
|
'test-d',
|
||||||
|
'generator',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
project: ['./tsconfig.eslint.json'],
|
||||||
|
sourceType: 'module',
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/autogen/**/*.ts', 'src/autogen/**/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'@stylistic/indent': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "i18n",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"main": "./built/index.js",
|
||||||
|
"types": "./built/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./built/index.d.ts",
|
||||||
|
"import": "./built/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"generate": "tsx scripts/generateLocaleInterface.ts",
|
||||||
|
"verify": "tsx scripts/verify.ts",
|
||||||
|
"build": "tsx ./build.ts",
|
||||||
|
"watch": "nodemon -w package.json -e json --exec \"tsx ./build.ts --watch\"",
|
||||||
|
"tsd": "tsd",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "pnpm typecheck && pnpm eslint",
|
||||||
|
"lint:fix": "pnpm eslint --fix"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"built"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/js-yaml": "4.0.9",
|
||||||
|
"@types/node": "24.10.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
|
"chokidar": "4.0.3",
|
||||||
|
"esbuild": "0.27.0",
|
||||||
|
"execa": "9.6.0",
|
||||||
|
"glob": "11.1.0",
|
||||||
|
"nodemon": "3.1.11",
|
||||||
|
"tsx": "4.20.6",
|
||||||
|
"typescript": "5.9.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "4.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import ts from 'typescript';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
const parameterRegExp = /\{(\w+)\}/g;
|
||||||
|
|
||||||
|
interface LocaleRecord {
|
||||||
|
[key: string]: string | LocaleRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMemberType(item: string | LocaleRecord): ts.TypeNode {
|
||||||
|
if (typeof item !== 'string') {
|
||||||
|
return ts.factory.createTypeLiteralNode(createMembers(item));
|
||||||
|
}
|
||||||
|
const parameters = Array.from(
|
||||||
|
item.matchAll(parameterRegExp),
|
||||||
|
([, parameter]) => parameter,
|
||||||
|
);
|
||||||
|
return parameters.length
|
||||||
|
? ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
|
[
|
||||||
|
ts.factory.createUnionTypeNode(
|
||||||
|
parameters.map((parameter) =>
|
||||||
|
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(parameter)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMembers(record: LocaleRecord): ts.TypeElement[] {
|
||||||
|
return Object.entries(record).map(([k, v]) => {
|
||||||
|
const node = ts.factory.createPropertySignature(
|
||||||
|
undefined,
|
||||||
|
ts.factory.createStringLiteral(k),
|
||||||
|
undefined,
|
||||||
|
createMemberType(v),
|
||||||
|
);
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
node,
|
||||||
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
||||||
|
`*
|
||||||
|
* ${v.replace(/\n/g, '\n * ')}
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateLocaleInterface(localesDir: string): Promise<void> {
|
||||||
|
const locale = yaml.load(fs.readFileSync(`${localesDir}/ja-JP.yml`, 'utf-8').toString()) as LocaleRecord;
|
||||||
|
const members = createMembers(locale);
|
||||||
|
|
||||||
|
const elements: ts.Statement[] = [
|
||||||
|
ts.factory.createImportDeclaration(
|
||||||
|
undefined,
|
||||||
|
ts.factory.createImportClause(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
ts.factory.createNamedImports([
|
||||||
|
ts.factory.createImportSpecifier(
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
),
|
||||||
|
ts.factory.createImportSpecifier(
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
ts.factory.createStringLiteral('../types.js'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
ts.factory.createInterfaceDeclaration(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||||
|
ts.factory.createIdentifier('Locale'),
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
||||||
|
ts.factory.createExpressionWithTypeArguments(
|
||||||
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
members,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
||||||
|
' eslint-disable ',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' This file is generated by scripts/generateLocaleInterface.ts',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' Do not edit this file directly.',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const printed = ts
|
||||||
|
.createPrinter({
|
||||||
|
newLine: ts.NewLineKind.LineFeed,
|
||||||
|
})
|
||||||
|
.printList(
|
||||||
|
ts.ListFormat.MultiLine,
|
||||||
|
ts.factory.createNodeArray(elements),
|
||||||
|
ts.createSourceFile(
|
||||||
|
'locale.ts',
|
||||||
|
'',
|
||||||
|
ts.ScriptTarget.ESNext,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TS,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const autogenDir = `${__dirname}/../src/autogen`;
|
||||||
|
fs.mkdirSync(autogenDir, { recursive: true });
|
||||||
|
|
||||||
|
// 一瞬ファイルが存在しなくなって途切れる→不安定になるらしいので、リネームで対処
|
||||||
|
fs.writeFileSync(`${autogenDir}/_locale.ts`, printed, 'utf-8');
|
||||||
|
fs.renameSync(`${autogenDir}/_locale.ts`, `${autogenDir}/locale.ts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// スクリプトとして直接実行された場合
|
||||||
|
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
||||||
|
if (isMain) {
|
||||||
|
await generateLocaleInterface(resolve(__dirname, '../../../locales'));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
let valid = true;
|
||||||
|
|
||||||
|
interface LocaleRecord {
|
||||||
|
[key: string]: string | LocaleRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorData {
|
||||||
|
expected?: string;
|
||||||
|
actual?: string;
|
||||||
|
parameter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeError(type: string, lang: string, tree: string, data: ErrorData): void {
|
||||||
|
process.stderr.write(JSON.stringify({ type, lang, tree, data }));
|
||||||
|
process.stderr.write('\n');
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify(expected: LocaleRecord, actual: LocaleRecord, lang: string, trace?: string): void {
|
||||||
|
for (const key in expected) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(actual, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof expected[key] === 'object') {
|
||||||
|
if (typeof actual[key] !== 'object') {
|
||||||
|
writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'object', actual: typeof actual[key] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
verify(expected[key] as LocaleRecord, actual[key] as LocaleRecord, lang, trace ? `${trace}.${key}` : key);
|
||||||
|
} else if (typeof expected[key] === 'string') {
|
||||||
|
switch (typeof actual[key]) {
|
||||||
|
case 'object':
|
||||||
|
writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'string', actual: 'object' });
|
||||||
|
break;
|
||||||
|
case 'undefined':
|
||||||
|
continue;
|
||||||
|
case 'string': {
|
||||||
|
const expectedParameters = new Set((expected[key] as string).match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
|
||||||
|
const actualParameters = new Set((actual[key] as string).match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1)));
|
||||||
|
for (const parameter of expectedParameters) {
|
||||||
|
if (!actualParameters.has(parameter)) {
|
||||||
|
writeError('missing_parameter', lang, trace ? `${trace}.${key}` : key, { parameter });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// index.tsはtsのまま動かすことを想定していない(ビルド成果物を外部に公開する).
|
||||||
|
// よってビルド後のものを検証する
|
||||||
|
const locales = await import('../built/index.js');
|
||||||
|
const { 'ja-JP': original, ...verifiees } = locales as unknown as Record<string, LocaleRecord>;
|
||||||
|
|
||||||
|
for (const lang in verifiees) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(locales, lang)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
verify(original, verifiees[lang], lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file is generated by locales/generateDTS.js
|
// This file is generated by scripts/generateLocaleInterface.ts
|
||||||
// Do not edit this file directly.
|
// Do not edit this file directly.
|
||||||
declare const kParameters: unique symbol;
|
import { type ILocale, type ParameterizedString } from "../types.js";
|
||||||
export type ParameterizedString<T extends string = string> = string & {
|
|
||||||
[kParameters]: T;
|
|
||||||
};
|
|
||||||
export interface ILocale {
|
|
||||||
[_: string]: string | ParameterizedString | ILocale;
|
|
||||||
}
|
|
||||||
export interface Locale extends ILocale {
|
export interface Locale extends ILocale {
|
||||||
/**
|
/**
|
||||||
* 日本語
|
* 日本語
|
||||||
|
|
@ -13089,8 +13083,3 @@ export interface Locale extends ILocale {
|
||||||
"mfm": string;
|
"mfm": string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
|
||||||
[lang: string]: Locale;
|
|
||||||
};
|
|
||||||
export function build(): Locale;
|
|
||||||
export default locales;
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Languages Loader
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import type { Locale } from './autogen/locale.js';
|
||||||
|
import type { ILocale, ParameterizedString } from './types.js';
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
'ar-SA',
|
||||||
|
'ca-ES',
|
||||||
|
'cs-CZ',
|
||||||
|
'da-DK',
|
||||||
|
'de-DE',
|
||||||
|
'en-US',
|
||||||
|
'es-ES',
|
||||||
|
'fr-FR',
|
||||||
|
'id-ID',
|
||||||
|
'it-IT',
|
||||||
|
'ja-JP',
|
||||||
|
'ja-KS',
|
||||||
|
'kab-KAB',
|
||||||
|
'kn-IN',
|
||||||
|
'ko-KR',
|
||||||
|
'nl-NL',
|
||||||
|
'no-NO',
|
||||||
|
'pl-PL',
|
||||||
|
'pt-PT',
|
||||||
|
'ru-RU',
|
||||||
|
'sk-SK',
|
||||||
|
'th-TH',
|
||||||
|
'tr-TR',
|
||||||
|
'ug-CN',
|
||||||
|
'uk-UA',
|
||||||
|
'vi-VN',
|
||||||
|
'zh-CN',
|
||||||
|
'zh-TW',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type Language = typeof languages[number];
|
||||||
|
|
||||||
|
const primaries = {
|
||||||
|
'en': 'US',
|
||||||
|
'ja': 'JP',
|
||||||
|
'zh': 'CN',
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
type PrimaryLang = keyof typeof primaries;
|
||||||
|
|
||||||
|
type Locales = Record<Language, ILocale>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* オブジェクトを再帰的にマージする
|
||||||
|
*/
|
||||||
|
function merge<T extends ILocale>(...args: (T | ILocale | undefined)[]): T {
|
||||||
|
return args.reduce<ILocale>((a, c) => ({
|
||||||
|
...a,
|
||||||
|
...c,
|
||||||
|
...Object.entries(a)
|
||||||
|
.filter(([k]) => c && typeof c[k] === 'object')
|
||||||
|
.reduce<Record<string, ILocale[string]>>((acc, [k, v]) => {
|
||||||
|
acc[k] = merge(v as ILocale, (c as ILocale)[k] as ILocale);
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
}), {} as ILocale) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
|
||||||
|
*/
|
||||||
|
function clean (text: string) {
|
||||||
|
return text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
|
||||||
|
*/
|
||||||
|
function removeEmpty<T extends ILocale>(obj: T): T {
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (v === '') {
|
||||||
|
delete obj[k];
|
||||||
|
} else if (typeof v === 'object') {
|
||||||
|
removeEmpty(v as ILocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build(): Record<Language, Locale> {
|
||||||
|
// vitestの挙動を調整するため、一度ローカル変数化する必要がある
|
||||||
|
// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
|
||||||
|
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
|
||||||
|
const metaUrl = import.meta.url;
|
||||||
|
const locales = languages.reduce<Locales>((a, lang) => {
|
||||||
|
a[lang] = (yaml.load(clean(fs.readFileSync(new URL(`./locales/${lang}.yml`, metaUrl), 'utf-8'))) ?? {}) as ILocale;
|
||||||
|
return a;
|
||||||
|
}, {} as Locales);
|
||||||
|
|
||||||
|
removeEmpty(locales);
|
||||||
|
|
||||||
|
return Object.entries(locales).reduce<Record<Language, Locale>>((a, [k, v]) => {
|
||||||
|
const lang = k.split('-')[0];
|
||||||
|
const key = k as Language;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'ja-JP':
|
||||||
|
a[key] = v as Locale;
|
||||||
|
break;
|
||||||
|
case 'ja-KS':
|
||||||
|
case 'en-US':
|
||||||
|
a[key] = merge<Locale>(locales['ja-JP'] as Locale, v);
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const primaryLang = lang as PrimaryLang;
|
||||||
|
const primaryKey = (lang in primaries ? `${lang}-${primaries[primaryLang]}` : undefined) as Language | undefined;
|
||||||
|
a[key] = merge<Locale>(
|
||||||
|
locales['ja-JP'] as Locale,
|
||||||
|
locales['en-US'],
|
||||||
|
primaryKey ? locales[primaryKey] : {},
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}, {} as Record<Language, Locale>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const locales = build() as {
|
||||||
|
[lang: string]: Locale;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フロントエンド用の locale JSON を書き出す
|
||||||
|
* Service Worker が HTTP 経由で取得するために必要
|
||||||
|
* @param destDir 出力先ディレクトリ(例: built/_frontend_dist_/locales)
|
||||||
|
* @param version バージョン文字列(ファイル名とJSON内に埋め込まれる)
|
||||||
|
*/
|
||||||
|
async function writeFrontendLocalesJson(destDir: string, version: string): Promise<void> {
|
||||||
|
const { mkdir, writeFile } = await import('node:fs/promises');
|
||||||
|
const { resolve } = await import('node:path');
|
||||||
|
|
||||||
|
await mkdir(destDir, { recursive: true });
|
||||||
|
|
||||||
|
const builtLocales = build();
|
||||||
|
const v = { '_version_': version };
|
||||||
|
|
||||||
|
for (const [lang, locale] of Object.entries(builtLocales)) {
|
||||||
|
await writeFile(
|
||||||
|
resolve(destDir, `${lang}.${version}.json`),
|
||||||
|
JSON.stringify({ ...locale, ...v }),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { locales, build, writeFrontendLocalesJson };
|
||||||
|
export type { Language, Locale, ILocale, ParameterizedString };
|
||||||
|
export default locales;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare const kParameters: unique symbol;
|
||||||
|
|
||||||
|
export type ParameterizedString<T extends string = string> = string & {
|
||||||
|
[kParameters]: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ILocale {
|
||||||
|
[_: string]: string | ParameterizedString | ILocale;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"scripts/**/*.ts",
|
||||||
|
"build.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import * as esbuild from 'esbuild';
|
import * as esbuild from 'esbuild';
|
||||||
import locales from '../../locales/index.js';
|
import locales from 'i18n';
|
||||||
import meta from '../../package.json' with { type: 'json' };
|
import meta from '../../package.json' with { type: 'json' };
|
||||||
|
|
||||||
const watch = process.argv[2]?.includes('watch');
|
const watch = process.argv[2]?.includes('watch');
|
||||||
|
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"i18n": "workspace:*",
|
||||||
"esbuild": "0.27.0",
|
"esbuild": "0.27.0",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
import { get, set } from 'idb-keyval';
|
import { get, set } from 'idb-keyval';
|
||||||
import { I18n } from '@@/js/i18n.js';
|
import { I18n } from '@@/js/i18n.js';
|
||||||
import type { Locale } from '../../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
|
|
||||||
class SwLang {
|
class SwLang {
|
||||||
public cacheName = `mk-cache-${_VERSION_}`;
|
public cacheName = `mk-cache-${_VERSION_}`;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { get } from 'idb-keyval';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { PushNotificationDataMap } from '@/types.js';
|
import type { PushNotificationDataMap } from '@/types.js';
|
||||||
import type { I18n } from '@@/js/i18n.js';
|
import type { I18n } from '@@/js/i18n.js';
|
||||||
import type { Locale } from '../../../locales/index.js';
|
import type { Locale } from 'i18n';
|
||||||
import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js';
|
import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js';
|
||||||
import { swLang } from '@/scripts/lang.js';
|
import { swLang } from '@/scripts/lang.js';
|
||||||
import * as swos from '@/scripts/operations.js';
|
import * as swos from '@/scripts/operations.js';
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ importers:
|
||||||
globals:
|
globals:
|
||||||
specifier: 16.5.0
|
specifier: 16.5.0
|
||||||
version: 16.5.0
|
version: 16.5.0
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:packages/i18n
|
||||||
ncp:
|
ncp:
|
||||||
specifier: 2.0.0
|
specifier: 2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|
@ -785,6 +788,9 @@ importers:
|
||||||
frontend-shared:
|
frontend-shared:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../frontend-shared
|
version: link:../frontend-shared
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
icons-subsetter:
|
icons-subsetter:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../icons-subsetter
|
version: link:../icons-subsetter
|
||||||
|
|
@ -1086,6 +1092,9 @@ importers:
|
||||||
estree-walker:
|
estree-walker:
|
||||||
specifier: 3.0.3
|
specifier: 3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
magic-string:
|
magic-string:
|
||||||
specifier: 0.30.21
|
specifier: 0.30.21
|
||||||
version: 0.30.21
|
version: 0.30.21
|
||||||
|
|
@ -1147,6 +1156,9 @@ importers:
|
||||||
frontend-shared:
|
frontend-shared:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../frontend-shared
|
version: link:../frontend-shared
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
icons-subsetter:
|
icons-subsetter:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../icons-subsetter
|
version: link:../icons-subsetter
|
||||||
|
|
@ -1286,6 +1298,9 @@ importers:
|
||||||
|
|
||||||
packages/frontend-shared:
|
packages/frontend-shared:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
misskey-js:
|
misskey-js:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../misskey-js
|
version: link:../misskey-js
|
||||||
|
|
@ -1318,6 +1333,46 @@ importers:
|
||||||
specifier: 10.2.0
|
specifier: 10.2.0
|
||||||
version: 10.2.0(eslint@9.39.1)
|
version: 10.2.0(eslint@9.39.1)
|
||||||
|
|
||||||
|
packages/i18n:
|
||||||
|
dependencies:
|
||||||
|
js-yaml:
|
||||||
|
specifier: 4.1.1
|
||||||
|
version: 4.1.1
|
||||||
|
devDependencies:
|
||||||
|
'@types/js-yaml':
|
||||||
|
specifier: 4.0.9
|
||||||
|
version: 4.0.9
|
||||||
|
'@types/node':
|
||||||
|
specifier: 24.10.1
|
||||||
|
version: 24.10.1
|
||||||
|
'@typescript-eslint/eslint-plugin':
|
||||||
|
specifier: 8.47.0
|
||||||
|
version: 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
specifier: 8.47.0
|
||||||
|
version: 8.47.0(eslint@9.39.1)(typescript@5.9.3)
|
||||||
|
chokidar:
|
||||||
|
specifier: 4.0.3
|
||||||
|
version: 4.0.3
|
||||||
|
esbuild:
|
||||||
|
specifier: 0.27.0
|
||||||
|
version: 0.27.0
|
||||||
|
execa:
|
||||||
|
specifier: 9.6.0
|
||||||
|
version: 9.6.0
|
||||||
|
glob:
|
||||||
|
specifier: 11.1.0
|
||||||
|
version: 11.1.0
|
||||||
|
nodemon:
|
||||||
|
specifier: 3.1.11
|
||||||
|
version: 3.1.11
|
||||||
|
tsx:
|
||||||
|
specifier: 4.20.6
|
||||||
|
version: 4.20.6
|
||||||
|
typescript:
|
||||||
|
specifier: 5.9.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/icons-subsetter:
|
packages/icons-subsetter:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tabler/icons-webfont':
|
'@tabler/icons-webfont':
|
||||||
|
|
@ -1519,6 +1574,9 @@ importers:
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: 0.27.0
|
specifier: 0.27.0
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
idb-keyval:
|
idb-keyval:
|
||||||
specifier: 6.2.2
|
specifier: 6.2.2
|
||||||
version: 6.2.2
|
version: 6.2.2
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ packages:
|
||||||
- packages/frontend
|
- packages/frontend
|
||||||
- packages/frontend-builder
|
- packages/frontend-builder
|
||||||
- packages/frontend-embed
|
- packages/frontend-embed
|
||||||
|
- packages/i18n
|
||||||
- packages/icons-subsetter
|
- packages/icons-subsetter
|
||||||
- packages/sw
|
- packages/sw
|
||||||
- packages/misskey-js
|
- packages/misskey-js
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ import * as yaml from 'js-yaml';
|
||||||
import postcss from 'postcss';
|
import postcss from 'postcss';
|
||||||
import * as terser from 'terser';
|
import * as terser from 'terser';
|
||||||
|
|
||||||
import { build as buildLocales } from '../locales/index.js';
|
import { locales } from 'i18n';
|
||||||
import generateDTS from '../locales/generateDTS.js';
|
|
||||||
import meta from '../package.json' with { type: "json" };
|
|
||||||
import buildTarball from './tarball.mjs';
|
import buildTarball from './tarball.mjs';
|
||||||
|
|
||||||
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
|
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
|
||||||
|
|
@ -23,86 +21,59 @@ const configPath = process.env.MISSKEY_CONFIG_YML
|
||||||
? path.resolve(configDir, 'test.yml')
|
? path.resolve(configDir, 'test.yml')
|
||||||
: path.resolve(configDir, 'default.yml');
|
: path.resolve(configDir, 'default.yml');
|
||||||
|
|
||||||
let locales = buildLocales();
|
|
||||||
|
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null);
|
return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyFrontendFonts() {
|
async function copyFrontendFonts() {
|
||||||
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
|
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
|
||||||
}
|
|
||||||
|
|
||||||
async function copyFrontendLocales() {
|
|
||||||
generateDTS();
|
|
||||||
|
|
||||||
await fs.mkdir('./built/_frontend_dist_/locales', { recursive: true });
|
|
||||||
|
|
||||||
const v = { '_version_': meta.version };
|
|
||||||
|
|
||||||
for (const [lang, locale] of Object.entries(locales)) {
|
|
||||||
await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyBackendViews() {
|
async function copyBackendViews() {
|
||||||
await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true });
|
await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildBackendScript() {
|
async function buildBackendScript() {
|
||||||
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
||||||
|
|
||||||
for (const file of [
|
for (const file of [
|
||||||
'./packages/backend/src/server/web/boot.js',
|
'./packages/backend/src/server/web/boot.js',
|
||||||
'./packages/backend/src/server/web/boot.embed.js',
|
'./packages/backend/src/server/web/boot.embed.js',
|
||||||
'./packages/backend/src/server/web/bios.js',
|
'./packages/backend/src/server/web/bios.js',
|
||||||
'./packages/backend/src/server/web/cli.js',
|
'./packages/backend/src/server/web/cli.js',
|
||||||
'./packages/backend/src/server/web/error.js',
|
'./packages/backend/src/server/web/error.js',
|
||||||
]) {
|
]) {
|
||||||
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
||||||
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
|
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
|
||||||
const { code } = await terser.minify(source, { toplevel: true });
|
const { code } = await terser.minify(source, { toplevel: true });
|
||||||
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code);
|
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildBackendStyle() {
|
async function buildBackendStyle() {
|
||||||
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
||||||
|
|
||||||
for (const file of [
|
for (const file of [
|
||||||
'./packages/backend/src/server/web/style.css',
|
'./packages/backend/src/server/web/style.css',
|
||||||
'./packages/backend/src/server/web/style.embed.css',
|
'./packages/backend/src/server/web/style.embed.css',
|
||||||
'./packages/backend/src/server/web/bios.css',
|
'./packages/backend/src/server/web/bios.css',
|
||||||
'./packages/backend/src/server/web/cli.css',
|
'./packages/backend/src/server/web/cli.css',
|
||||||
'./packages/backend/src/server/web/error.css'
|
'./packages/backend/src/server/web/error.css'
|
||||||
]) {
|
]) {
|
||||||
const source = await fs.readFile(file, { encoding: 'utf-8' });
|
const source = await fs.readFile(file, { encoding: 'utf-8' });
|
||||||
const { css } = await postcss([cssnano({ zindex: false })]).process(source, { from: undefined });
|
const { css } = await postcss([cssnano({ zindex: false })]).process(source, { from: undefined });
|
||||||
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, css);
|
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, css);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function build() {
|
async function build() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
copyFrontendFonts(),
|
copyFrontendFonts(),
|
||||||
copyFrontendLocales(),
|
copyBackendViews(),
|
||||||
copyBackendViews(),
|
buildBackendScript(),
|
||||||
buildBackendScript(),
|
buildBackendStyle(),
|
||||||
buildBackendStyle(),
|
|
||||||
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
|
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await build();
|
await build();
|
||||||
|
|
||||||
if (process.argv.includes('--watch')) {
|
|
||||||
const watcher = fs.watch('./locales');
|
|
||||||
for await (const event of watcher) {
|
|
||||||
const filename = event.filename?.replaceAll('\\', '/');
|
|
||||||
if (/^[a-z]+-[A-Z]+\.yml/.test(filename)) {
|
|
||||||
console.log(`update ${filename} ...`)
|
|
||||||
locales = buildLocales();
|
|
||||||
await copyFrontendLocales()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ const fs = require('fs');
|
||||||
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
|
||||||
|
|
||||||
|
fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true });
|
||||||
|
fs.rmSync(__dirname + '/../packages/i18n/node_modules', { recursive: true, force: true });
|
||||||
|
|
||||||
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/misskey-js/node_modules', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/misskey-js/node_modules', { recursive: true, force: true });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const fs = require('fs');
|
||||||
fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/icons-subsetter/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/icons-subsetter/built', { recursive: true, force: true });
|
||||||
|
fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
|
||||||
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
|
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ await execa('pnpm', ['clean'], {
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// アセットのビルドで依存しているので一番最初に必要
|
||||||
|
await execa('pnpm', ['--filter', 'i18n', 'build'], {
|
||||||
|
cwd: _dirname + '/../',
|
||||||
|
stdout: process.stdout,
|
||||||
|
stderr: process.stderr,
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
execa('pnpm', ['build-pre'], {
|
execa('pnpm', ['build-pre'], {
|
||||||
cwd: _dirname + '/../',
|
cwd: _dirname + '/../',
|
||||||
|
|
@ -38,6 +45,11 @@ await Promise.all([
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
}),
|
}),
|
||||||
|
execa('pnpm', ['--filter', 'misskey-js', 'build'], {
|
||||||
|
cwd: _dirname + '/../',
|
||||||
|
stdout: process.stdout,
|
||||||
|
stderr: process.stderr,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
execa('pnpm', ['build-pre', '--watch'], {
|
execa('pnpm', ['build-pre', '--watch'], {
|
||||||
|
|
@ -88,6 +100,12 @@ execa('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], {
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
execa('pnpm', ['--filter', 'i18n', 'watch', '--no-clean'], {
|
||||||
|
cwd: _dirname + '/../',
|
||||||
|
stdout: process.stdout,
|
||||||
|
stderr: process.stderr,
|
||||||
|
});
|
||||||
|
|
||||||
execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
|
execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
|
||||||
cwd: _dirname + '/../',
|
cwd: _dirname + '/../',
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue