Open lesmo opened 4 years ago
@lesmo Hi 👋
I'm not a big user of SSR but it should be do-able. The main pain point is currently to parse and send the Accept-Language
header to the module to have valid data at startup.
Imagine:
// on server
import { parseAcceptLanguageHeader } from "react-native-localize/server"
// …
render(<App languages={parseAcceptLanguageHeader(header)} />)
In your app:
generateConstants(languages)
But could it be enough? getNumberFormatSettings
, getTimeZone
, uses24HourClock
depends on browser Intl
API.
Another solution could be to switch to lazy, synchronous getters.
Oh I like that idea! Passing Accept-Language
should be an easy task from most of the major SSR libraries (I'm trying to get this to work with [razzle]() btw).
The browser API could be polyfilled for SSR... 🥁 drums for dramatic effect... 🥁 with intl perhaps? Using it as an optional dependency would allow for this magic to happen.
Intl.js
seems unmaintained 😞
An easy start could be lazy evaluation: stop generating constants at start, but instead only when requested. It would make SSR related future work easier.
I've looked into that and while it hasn't been updated, I wouldn't say it's unmaintained... just dusty. 😜
While researching for some bugs on my Android build, I found some mentions of that polyfill as a solution to the missing Intl
object and some problems due to missing implementations. As far as I could tell, I think those missing implementations wouldn't make much of a difference... but still, after some thought even if that polyfill was maintained, I don't think it's a good idea to have it be a dependency to this lib. It's easier to say "wanna use it for SSR? make sure you have this or this", which brings me to my next point:
I think a reasonable solution would be to warn and maybe even have users of react-native-localize
use a Node environment with support for internationalization when pretending to use it for SSR. Node has built-in support for the required stuff, although some builds might not have it built-in... but that's for the implementor to solve (maybe even with the polyfill).
I believe that's a nice solution. Just rewrite the web stuff to be lazy, and document that it'll need to be run on Node with proper support.
I'll see if I can put together a PR. 😀
@lesmo I just published a new version with lazy getters on the web version: https://github.com/react-native-community/react-native-localize/releases/tag/1.3.3
Now we are free of code that calls navigator
or window
objects at module init. 😌
It should be easier to work on SSR support (we still need to find a way to parse the accept-language
header and pass it down to the functions)!
Thanks! I'll try this out!
I've been thinking about the Accept-Language
thing, and it's quite difficult to solve with the current API. A solution I thought was making the RNLocalize
an instantiable object that can be given an override param. Borrowing from react-navigation
example for SSR (and actually quite similar to my solution), I imagined:
expressApp.get("/*", (req, res) => {
const { path, query } = req;
const runtimeLocales = parser.parse(req.get('Accept-Language');
const localize = new RNLocalize({ runtimeLocales });
const { navigation, title, options } = handleServerRequest(
AppNavigator.router,
path,
query
);
// register the app
AppRegistry.registerComponent('App', () => App);
// prerender the app
const { element, getStyleElement } = AppRegistry.getApplication('App', {
initialProps: { navigation, localize }, // now <App> has localize prop
});
const markup = renderToString(<AppNavigator navigation={navigation} />);
res.send(
`<!doctype html>
<html lang="">
<head>
<title>${title}</title>
<script src="main.js"></script>
</head>
<body>
<div id="root">${markup}</div>
</body>
</html>`
);
});
This way we can do:
// App.js
export default App = ({ localize }) => {
const lang = localize.findBestAvailableLanguage(['en', 'en-GB', 'fr', 'pr']);
return <AnAwesomeApp />
}
Or fancier, putting it inside a react context one could use hooks too:
// App.js
export default App = ({ runtimeLocales }) => {
return (
// Directly "override" the platform available languages with
// the ones from express
<LocalizeProvider platformLanguages={runtimeLocales}>
<AnAwesomeApp />
</LocalizeProvider>
)
}
// Somewhere.js
export const Somewhere = () => {
const localize = useLocalize();
return (
<Text>{localize.getCountry()}</Text>
);
};
This way there's no direct dependency on global variables and it's SSR friendly... but it's a massive refactor, and I'm not sure it's a good idea (yet) or if could benefit other use cases at all. Something for like v2 maybe? 😅
@lesmo Totally in the v2 TODO list 🙂
Hi guys, I would be interested in this as well. Do you have some roadmap (for the v2) on when this will be implemented?
Cheers!
Just a heads up, Intl.js will no longer be maintained so... I guess the best route forward would be to consider having implementers make sure they run Node with Intl compiled into the final binary.
FormatJS offers a full set of polyfills: https://formatjs.io/docs/polyfills I see no mentions of NodeJS support, but it might be compatible.
FormatJS offers a full set of polyfills: https://formatjs.io/docs/polyfills I see no mentions of NodeJS support, but it might be compatible.
Actually, the home page says it does so... that's the one!
We (formatjs) do support Node, although Node 14+ has almost everything you need (sans the bugs that we fixed)
Feature Request
Support for SSR. Current index.web.js is directly calling browser-only objects, which is not possible while being rendered on the server.
Why it is needed
Because localize all the things everywhere!
Possible implementation
I noticed the calls are to
navigator
andwindow
objects. Those statements are called as soon as any code importsreact-native-localize
, so it would be necessary to "delay" that to a later stage, or call them lazily or even conditionally. I don't know how this module's internals work... yet 😏Code sample
This is a conditional example that would render properly on SSR:
The only complication is how to make
constants
somehow wait or be populated until the browser is ready without breaking anything. This could work:But I'm not sure if an undefined
constants
would break something, and there would of course need to be a way to know from the client which locale to use. I'll get back if I come up with something.