-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(TSE-1144): Add SSR possibility on Next (#102)
Co-authored-by: Ashley Hsiao <ashley.hsiao@phrase.com>
- Loading branch information
1 parent
34af114
commit 6bac6e5
Showing
21 changed files
with
3,409 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts | ||
|
||
# Yarn | ||
.yarn/* | ||
*/.yarn | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/sdks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). It shows how to integrate the [PhraseApp In-Context Editor](https://phraseapp.com/) using [react-intl](https://github.com/yahoo/react-intl) for localization via the [react-intl-phraseapp](https://github.com/phrase/react-intl-phraseapp) plugin. | ||
|
||
## Getting Started | ||
|
||
First, run the development server: | ||
|
||
```bash | ||
yarn && yarn dev | ||
``` | ||
|
||
Open [http://localhost:3000/client](http://localhost:3000/client) with your browser to see the client component demo | ||
Open [http://localhost:3000/server](http://localhost:3000/server) with your browser to see the server component demo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** @type {import('next').NextConfig} */ | ||
const nextConfig = { | ||
experimental: { | ||
serverActions: true, | ||
}, | ||
} | ||
|
||
module.exports = nextConfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "next-ssr-demo", | ||
"version": "0.1.0", | ||
"private": true, | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start", | ||
"lint": "next lint" | ||
}, | ||
"dependencies": { | ||
"@types/node": "20.5.9", | ||
"@types/react": "18.2.21", | ||
"@types/react-dom": "18.2.7", | ||
"eslint": "8.48.0", | ||
"eslint-config-next": "13.4.19", | ||
"next": "13.4.18", | ||
"react": "18.2.0", | ||
"react-dom": "18.2.0", | ||
"react-intl": "^6.4.4", | ||
"react-intl-phraseapp": "link:../../dist", | ||
"typescript": "5.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.App { | ||
text-align: center; | ||
padding: 32px; | ||
} | ||
|
||
|
||
body > iframe | ||
{ | ||
display: none; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use client' | ||
|
||
import './App.css'; | ||
import { FormattedMessage, useIntl } from 'react-intl-phraseapp'; | ||
import ClassTestComponent from './ClassTestComponent'; | ||
|
||
const App = () => { | ||
const { formatMessage } = useIntl(); | ||
|
||
return ( | ||
<div className="App"> | ||
<h2>This is a simple demo of react-intl Next.js client integration of the In-Context Editor</h2> | ||
<p> | ||
{/* one can use FormattedMessage component */} | ||
<FormattedMessage | ||
id="hero_title" | ||
defaultMessage={`We hope this example will help you integrate PhraseApp into your react app using react-intl`} | ||
/> | ||
</p> | ||
<p> | ||
{/* one can use formatMessage from useIntl hook */} | ||
{ formatMessage({ id: 'create_this_key', defaultMessage: 'Uncreated key to show creation capabilities.' }) } | ||
</p> | ||
|
||
<ClassTestComponent translation="advantages_text" /> | ||
</div> | ||
); | ||
} | ||
|
||
export default App; |
39 changes: 39 additions & 0 deletions
39
examples/next-ssr-demo/src/app/client/ClassTestComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use client' | ||
|
||
import { Component } from 'react'; | ||
import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl-phraseapp'; | ||
|
||
type Props = WrappedComponentProps & { translation: string }; | ||
|
||
class ClassTestComponent extends Component<Props> { | ||
render() { | ||
return ( | ||
<div> | ||
<h3>Class component:</h3> | ||
<p> | ||
{/* one can use Formatted Message */} | ||
<FormattedMessage | ||
id="integrate_text" | ||
defaultMessage={`We hope this example will help you integrate PhraseApp into your react app using react-intl`} | ||
/> | ||
</p> | ||
<p> | ||
<FormattedMessage | ||
id="create_this_key" | ||
defaultMessage={`Uncreated key to show creation capabilities.`} | ||
/> | ||
</p> | ||
<p> | ||
{/* or use HOC injectIntl wrapper to use the intl.formatMessage prop | ||
WrappedComponentProps must be typed with the component's props! */} | ||
{this.props.intl.formatMessage({ id: this.props.translation })} | ||
</p> | ||
<p> | ||
{this.props.intl.formatMessage({id: 'variable_text'}, {variable: <b>SomeVariableForTest</b>})} | ||
</p> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default injectIntl(ClassTestComponent); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use client' | ||
|
||
import './App.css'; | ||
import { initializePhraseAppEditor } from 'react-intl-phraseapp'; | ||
import { IntlProvider } from 'react-intl'; | ||
import App from './App'; | ||
|
||
export const page = () => { | ||
const config = { | ||
projectId: '00000000000000004158e0858d2fa45c', | ||
accountId: '0bed59e5', | ||
phraseEnabled: true, | ||
prefix: '[[__', | ||
suffix: '__]]' | ||
} | ||
|
||
const messages = { | ||
hero_title: "Enable the ICE by setting `phraseEnabled: true`!", | ||
advantages_text: "With the In-context Editor, clients are able to minimize errors by giving translators insight into the context of the translation and letting them edit the content right on the spot.", | ||
integrate_title: "Integrate the Phrase In-context Editor into your existing application", | ||
create_this_key: "This key doesn't exist yet, try creating it!", | ||
integrate_text: "We hope this example will help you integrate PhraseApp into your react app using react-intl", | ||
variable_text: "{variable} variable should show up when ICE is not enabled!" | ||
} | ||
|
||
initializePhraseAppEditor(config); | ||
|
||
return ( | ||
<IntlProvider locale="en" messages={messages}> | ||
<App/> | ||
</IntlProvider> | ||
); | ||
} | ||
|
||
export default page; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { Metadata } from 'next' | ||
import { Inter } from 'next/font/google' | ||
|
||
const inter = Inter({ subsets: ['latin'] }) | ||
|
||
export const metadata: Metadata = { | ||
title: 'Create Next App', | ||
description: 'Generated by create next app', | ||
} | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<html lang="en"> | ||
<body className={inter.className}>{children}</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.App { | ||
text-align: center; | ||
padding: 32px; | ||
} | ||
|
||
|
||
body > iframe | ||
{ | ||
display: none; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'server-only' | ||
|
||
import './App.css'; | ||
import { getIntl } from './utils'; | ||
|
||
export default async function App() { | ||
const { formatMessage } = getIntl(); | ||
return ( | ||
<div className="App"> | ||
<h2>This is a simple demo of react-intl Next.js SSR integration of the In-Context Editor</h2> | ||
<p> | ||
{/* FormattedMessage cannot be used because of internal context use that is not available on server components */} | ||
</p> | ||
<p> | ||
{/* One can use formatMessage as defined by getIntl */} | ||
{ formatMessage({ id: 'hero_title'}) } | ||
</p> | ||
<p> | ||
{ formatMessage({ id: 'advantages_text'}) } | ||
</p> | ||
<p> | ||
{ formatMessage({ id: 'create_this_key'}) } | ||
</p> | ||
<p> | ||
{ formatMessage({ id: 'integrate_text'}) } | ||
</p> | ||
<p> | ||
{ formatMessage({ id: 'variable_text'}) } | ||
</p> | ||
|
||
{/* Class components cannot be SSR */} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
'server-only' | ||
|
||
import './App.css'; | ||
import App from './App'; | ||
import Script from 'next/script'; | ||
import { createPhraseAppEditorScript } from 'react-intl-phraseapp/functions'; | ||
|
||
export default async function Page() { | ||
// Adjust your Project and Account ids | ||
// Basically you can use ICE with anything as long as you can manage to convert your translation library's key output to [[__phrase_some.key.id__]] | ||
// This format will allow ICE to catch the keys and should then be able to process them | ||
const config = { | ||
projectId: '00000000000000004158e0858d2fa45c', | ||
accountId: '0bed59e5', | ||
|
||
} | ||
|
||
return ( | ||
<div> | ||
<App/> | ||
<Script id="phrase-script" dangerouslySetInnerHTML={{__html: createPhraseAppEditorScript(config)}} /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
'server-only' | ||
// import { IntlShape, createIntl, createIntlCache } from "@formatjs/intl"; NOTE the @formatjs/intl, not react-intl | ||
import { useSSRIntl as initIntl } from "react-intl-phraseapp/useSSRIntl"; // If you import "react-intl-phraseapp" you will get useContext related error | ||
|
||
export function getIntl() { | ||
const locale = 'en' | ||
const messages = { | ||
hero_title: "Enable the ICE by setting `phraseEnabled: true`!", | ||
advantages_text: "With the In-context Editor, clients are able to minimize errors by giving translators insight into the context of the translation and letting them edit the content right on the spot.", | ||
integrate_title: "Integrate the Phrase In-context Editor into your existing application", | ||
create_this_key: "This key doesn't exist yet, try creating it!", | ||
integrate_text: "We hope this example will help you integrate PhraseApp into your react app using react-intl", | ||
variable_text: "{variable} variable should show up when ICE is not enabled!" | ||
} | ||
|
||
const intl = initIntl({locale, messages}) | ||
|
||
// Or you can do something like this | ||
// const cache = createIntlCache(); | ||
// const intl = { | ||
// ...createIntl({locale, messages}, cache), | ||
// // Add condition here to overwrite formatMessage with this format when needed | ||
// formatMessage: ( | ||
// messageDescriptor: Parameters<IntlShape['formatMessage']>[0], | ||
// _values?: Parameters<IntlShape['formatMessage']>[1], | ||
// _opts?: Parameters<IntlShape['formatMessage']>[2] | ||
// ) => "[[__phrase_" + messageDescriptor.id + "__]]" | ||
// } | ||
|
||
return intl | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"lib": [ | ||
"dom", | ||
"dom.iterable", | ||
"esnext" | ||
], | ||
"allowJs": true, | ||
"skipLibCheck": true, | ||
"strict": true, | ||
"noEmit": true, | ||
"esModuleInterop": true, | ||
"module": "esnext", | ||
"moduleResolution": "bundler", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"jsx": "preserve", | ||
"incremental": true, | ||
"plugins": [ | ||
{ | ||
"name": "next" | ||
} | ||
], | ||
"paths": { | ||
"@/*": [ | ||
"./src/*" | ||
] | ||
}, | ||
"forceConsistentCasingInFileNames": true | ||
}, | ||
"include": [ | ||
"next-env.d.ts", | ||
"**/*.ts", | ||
"**/*.tsx", | ||
".next/types/**/*.ts" | ||
], | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
} |
Oops, something went wrong.