Skip to content

Commit

Permalink
Feat: plugin for intl (#6863)
Browse files Browse the repository at this point in the history
* feat: plugin for intl

* chore: readme

* chore: changelog

* Update runtime.tsx

* Update README.md

* feat: support intl in server
  • Loading branch information
ClarkXia authored May 6, 2024
1 parent 5c40dc9 commit 8dada9b
Show file tree
Hide file tree
Showing 28 changed files with 459 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-actors-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/app': patch
---

feat: support generator api to inject code in server entry
1 change: 1 addition & 0 deletions examples/with-intl/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chrome 55
6 changes: 6 additions & 0 deletions examples/with-intl/ice.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from '@ice/app';
import intl from '@ice/plugin-intl';

export default defineConfig(() => ({
plugins: [intl()],
}));
23 changes: 23 additions & 0 deletions examples/with-intl/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@examples/with-intl",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "ice start",
"build": "ice build"
},
"description": "",
"author": "",
"license": "MIT",
"dependencies": {
"@ice/app": "workspace:*",
"@ice/plugin-intl": "workspace:*",
"@ice/runtime": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2"
}
}
Binary file added examples/with-intl/public/favicon.ico
Binary file not shown.
8 changes: 8 additions & 0 deletions examples/with-intl/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineAppConfig } from 'ice';
import type { LocaleConfig } from '@ice/plugin-intl/types';

export default defineAppConfig(() => ({}));

export const locale: LocaleConfig = {
getLocale: () => 'en-US',
};
22 changes: 22 additions & 0 deletions examples/with-intl/src/document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, Title, Links, Main, Scripts } from 'ice';

function Document() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="description" content="ICE Demo" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Title />
<Links />
</head>
<body>
<Main />
<Scripts />
</body>
</html>
);
}

export default Document;
3 changes: 3 additions & 0 deletions examples/with-intl/src/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
font-size: 14px;
}
3 changes: 3 additions & 0 deletions examples/with-intl/src/locales/en-US.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
new: 'New',
};
3 changes: 3 additions & 0 deletions examples/with-intl/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"new": "新建"
}
10 changes: 10 additions & 0 deletions examples/with-intl/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { intl } from 'ice';

export default function Home() {
return (
<>
<h1>home</h1>
<button>{intl.formatMessage({ id: 'new' })}</button>
</>
);
}
1 change: 1 addition & 0 deletions examples/with-intl/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@ice/app/types" />
32 changes: 32 additions & 0 deletions examples/with-intl/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compileOnSave": false,
"buildOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"module": "esnext",
"target": "es6",
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"rootDir": "./",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"],
"ice": [".ice"]
}
},
"include": ["src", ".ice", "ice.config.*"],
"exclude": ["build", "public"]
}
9 changes: 7 additions & 2 deletions packages/ice/src/createService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,13 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
addEntryCode: (callback: (originalCode: string) => string) => {
entryCode = callback(entryCode);
},
addEntryImportAhead: (declarationData: Pick<DeclarationData, 'source'>) => {
generator.addDeclaration('entry', declarationData);
addEntryImportAhead: (declarationData: Pick<DeclarationData, 'source'>, type = 'client') => {
if (type === 'both' || type === 'server') {
generator.addDeclaration('entryServer', declarationData);
}
if (type === 'both' || type === 'client') {
generator.addDeclaration('entry', declarationData);
}
},
modifyRenderData: generator.modifyRenderData,
addDataLoaderImport: (declarationData: DeclarationData) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ice/src/service/runtimeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default class Generator {
this.rerender = false;
this.renderTemplates = [];
this.renderDataRegistration = [];
this.contentTypes = ['framework', 'frameworkTypes', 'routeConfigTypes', 'dataLoaderImport', 'runtimeOptions', 'entry'];
this.contentTypes = ['framework', 'frameworkTypes', 'routeConfigTypes', 'dataLoaderImport', 'runtimeOptions', 'entry', 'entryServer'];
// empty .ice before render
fse.emptyDirSync(path.join(rootDir, targetDir));
// add initial templates
Expand Down
2 changes: 1 addition & 1 deletion packages/ice/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type { CreateLoggerReturnType } from '../utils/logger.js';

type AddExport = (exportData: DeclarationData) => void;
type AddEntryCode = (callback: (code: string) => string) => void;
type AddEntryImportAhead = (exportData: Pick<DeclarationData, 'source'>) => void;
type AddEntryImportAhead = (exportData: Pick<DeclarationData, 'source'>, type?: string) => void;
type RemoveExport = (removeSource: string | string[]) => void;
type EventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
type GetExportList = (key: string, target?: string) => DeclarationData[];
Expand Down
2 changes: 2 additions & 0 deletions packages/ice/templates/core/entry.server.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './env.server';
<%- entryServer.imports %>
import * as runtime from '@ice/runtime/server';
<% if (hydrate) {-%>
import { commons, statics } from './runtime-modules';
Expand All @@ -21,6 +22,7 @@ import routesManifest from './route-manifest.json';
import routesConfig from './routes-config.bundle.mjs';
<% if (dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%>
<% if (hydrate) {-%><%- runtimeOptions.imports %><% } -%>

<% if (!hydrate) {-%>
// Do not inject runtime modules when render mode is document only.
const commons = [];
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-intl/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @ice/plugin-intl

## 1.0.0

- Initial release
73 changes: 73 additions & 0 deletions packages/plugin-intl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# @ice/plugin-intl

`@ice/plugin-intl` is a ice.js plugin. It provides a simple way to add internationalization support to your application.

> `@ice/plugin-intl` is based on `react-intl`.
## Install

```bash
$ npm i @ice/plugin-intl --save-dev
```

## Usage

Define the plugin in `ice.config.mts`:

```ts
import { defineConfig } from '@ice/app';
import intl from '@ice/plugin-intl';

export default defineConfig({
plugins: [
intl(),
],
});
```

Define locale config in `src/app.ts`:

```ts
import { defineAppConfig } from 'ice';
import type { LocaleConfig } from '@ice/plugin-intl/types';

export default defineAppConfig(() => ({}));

export const locale: LocaleConfig = {
// Cutomize getLocale method and other options supported by react-intl.
getLocale: () => 'en-US',
};
```

## Locales

Locales are defined in the `src/locales` directory. Each locale is defined in a separate file, with the locale name as the file name. For example, `en-US.ts`:

```ts
export default {
'app.title': 'My Application',
'app.welcome': 'Welcome to my application!',
};
```

Use the `useIntl` hook to access the current locale:

```tsx
import { useIntl } from 'ice';

export default function Home() {
const intl = useIntl();
console.log(intl.formatMessage({ id: 'new' }));
return <h1>home</h1>;
}
```

Use the `intl` function to access the current locale:

```tsx
import { intl } from 'ice';

function alertMessage() {
alert(intl.formatMessage({ id: 'app.welcome' }));
}
```
42 changes: 42 additions & 0 deletions packages/plugin-intl/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@ice/plugin-intl",
"version": "1.0.0",
"description": "react intl plugin for ice.js 3.",
"files": [
"esm",
"!esm/**/*.map",
"*.d.ts",
"templates"
],
"type": "module",
"main": "esm/index.js",
"module": "esm/index.js",
"types": "esm/index.d.ts",
"exports": {
".": "./esm/index.js",
"./runtime": "./esm/runtime.js",
"./types": "./esm/types.js"
},
"sideEffects": false,
"scripts": {
"watch": "tsc -w --sourceMap",
"build": "tsc"
},
"dependencies": {
"react-intl": "^6.0.0",
"fast-glob": "^3.3.2"
},
"devDependencies": {
"@ice/app": "^3.3.2",
"@ice/runtime": "^1.2.9",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/ice/tree/master/packages/plugin-intl"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions packages/plugin-intl/runtime.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './esm/runtime';
73 changes: 73 additions & 0 deletions packages/plugin-intl/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as path from 'path';
import { fileURLToPath } from 'url';
import fg from 'fast-glob';
import type { Plugin } from '@ice/app/types';

const _dirname = path.dirname(fileURLToPath(import.meta.url));

const plugin: Plugin = () => ({
name: 'plugin-intl',
setup: ({ generator, context, createLogger, watch }) => {
const { rootDir } = context;
const logger = createLogger('plugin-intl');

const renderLocaleEntry = (localeFiles: string[]) => {
const locales = [];
let localeExport = [];
localeFiles.forEach((file) => {
const filename = path.basename(file, path.extname(file));
// `-` is not allowed in import specifier.
const specifier = filename.replace('-', '_');
locales.push(`import ${specifier} from '@/locales/${filename}';`);
localeExport.push(`'${filename}': ${specifier},`);
});

generator.addRenderFile(
path.join(_dirname, '../templates/locales.ts.ejs'),
'locales.ts',
{
localeImport: locales.join('\n'),
localeExport: localeExport.join('\n '),
},
);
};
const globRule = 'src/locales/*.{ts,js,json}';
// Glob all locale files, and generate runtime options.
const localeFiles = fg.sync(globRule, { cwd: rootDir });
if (localeFiles.length > 0) {
// Filter the entry of locale files.
const mainEntry = localeFiles.find((file) => file.match(/index\.(ts|js|json)$/));
let runtimeSource = '';
if (mainEntry) {
runtimeSource = `@/locales/${path.basename(mainEntry)}`;
} else {
// Create a locale entry file to export all locale files.
renderLocaleEntry(localeFiles);

// Add watch event for locale files added or removed.
watch.addEvent([/src\/locales/, (event) => {
if (event === 'unlink' || event === 'add') {
const files = fg.sync(globRule, { cwd: rootDir });
renderLocaleEntry(files);
}
}]);
runtimeSource = './locales';

generator.addEntryImportAhead({
source: runtimeSource,
}, 'both');
}
} else {
logger.warn('No locale files found, please check the `src/locales` folder.');
}

// Add intl export from ice.
generator.addExport({
specifier: ['useIntl', 'intl'],
source: '@ice/plugin-intl/runtime',
});
},
runtime: '@ice/plugin-intl/runtime',
});

export default plugin;
Loading

0 comments on commit 8dada9b

Please sign in to comment.