diff --git a/content/es/guide/v10/signals.md b/content/es/guide/v10/signals.md new file mode 100644 index 000000000..df0c8a093 --- /dev/null +++ b/content/es/guide/v10/signals.md @@ -0,0 +1,585 @@ +--- +name: Signals (Señales) +description: 'Signals: estado reactivo componible con renderización automática' +--- + +# Signals + +Signals, señales en español, son primitivas reactivas para gestionar el estado de las aplicaciones. + +Lo que hacen únicos a los Signals es que los cambios de estado actualizan automáticamente los componentes y la interfaz de usuario de la forma más eficiente posible. La vinculación automática de estados y el seguimiento de dependencias permiten a Signals ofrecer una ergonomía y una productividad excelentes, al tiempo que elimina las pegas más comunes en la gestión de estados. + +Signals son eficaces en aplicaciones de cualquier tamaño, con ergonomías que aceleran el desarrollo de aplicaciones pequeñas y características de rendimiento que garantizan que las aplicaciones de cualquier tamaño sean rápidas por defecto. + +--- + +**Importante** + +Esta guía tratará sobre el uso de Signals en Preact, y aunque es aplicable tanto a la librería Core como a React, habrá algunas diferencias de uso. Las mejores referencias para su uso están en sus respectivas documentaciones: `@preact/signals-core`, `@preact/signals-react`. + +--- + +
+ +--- + +## Introducción + +Gran parte de la dificultad de la gestión de estados en JavaScript es reaccionar a los cambios de un valor dado, porque los valores no son directamente observables. Las soluciones suelen resolver este problema almacenando los valores en una variable y comprobando continuamente si han cambiado, lo cual es engorroso y no es ideal para el rendimiento. Idealmente, queremos una forma de expresar un valor que nos diga cuándo cambia. Eso es lo que hacen los Signals. + +En esencia, un signal es un objeto con una propiedad `.value` que contiene un valor. Esto tiene una característica importante: el valor de un signal puede cambiar, pero el signal en sí siempre permanece igual: + +```js +// --repl +import { signal } from "@preact/signals"; + +const count = signal(0); + +// Lee el valor de un signal al acceder a .value +console.log(count.value); // 0 + +// Actualiza el valor de un signal +count.value += 1; + +// El valor de la signal ha cambiado +console.log(count.value); // 1 +``` + +En Preact, cuando un signal se pasa a través de un arbol como props o contexto, sólo estamos pasando referencias al signal. El signal puede ser actualizado sin volver a renderizar ningún componente, ya que los componentes ven el signal y no su valor. Esto nos permite saltarnos todo el costoso trabajo de renderizado y saltar inmediatamente a cualquier componente en el árbol que actualmente acceda a la propiedad `.value` del signal. + +Los signals tienen una segunda caracteristica importante, y es que rastrean cuándo se accede a su valor y cuándo se actualiza. En Preact, al accceso a la propiedad `.value` de un signal dentro de un componente vuelve a renderizar automáticamente el componente cuando el valor del signal cambia. + +```jsx +// --repl +import { render } from "preact"; +// --repl-before +import { signal } from "@preact/signals"; + +// Crea un signal a la que se puede suscribir: +const count = signal(0); + +function Counter() { + // Al acceder a `.value` en un componente, se vuelve a mostrar automáticamente cuando cambia: + const value = count.value; + + const increment = () => { + // Un signal es actualizado al asignar a la propiedad `.value`: + count.value++; + } + + return ( +
+

Conteo: {value}

+ +
+ ); +} +// --repl-after +render(, document.getElementById("app")); +``` + +Por último, los signals están profundamente integrados en Preact para proporcionar el mejor rendimiento y ergonomía posibles. En el ejemplo anterior, accedimos a `count.value` para recuperar el valor actual del signal `count`, sin embargo esto es innecesario. En su lugar, podemos dejar que Preact haga todo el trabajo por nosotros utilizando el signal `count` directamente en JSX: + +```jsx +// --repl +import { render } from "preact"; +// --repl-before +import { signal } from "@preact/signals"; + +const count = signal(0); + +function Counter() { + return ( +
+

Conteo: {count}

+ +
+ ); +} +// --repl-after +render(, document.getElementById("app")); +``` + +## Instalación + +Los signals pueden ser instalado al añadir el paquete `@preact/signals` a tu proyecto: + +```bash +npm install @preact/signals +``` + +Una vez instalado por medio del gestor de paquetes de preferencia, estas listo para importalo en tu aplicación. + +## Ejemplo de Uso + +Vamos a utilizar los signals en un escenario del mundo real. Vamos a construir una aplicación de lista de tareas, donde se puede añadir y eliminar elementos en una lista de tareas. Empezaremos modelando el estado. Primero necesitaremos un signal que contengan el listado de tareas, el cual puede ser representado con un `Array`: + +```jsx +import { signal } from "@preact/signals"; + +const todos = signal([ + { text: "Comprar víveres" }, + { text: "Pasear el perro" }, +]); +``` + +Para permitir al usuario introduzca el texto de un nuevo elemento, necesitaremos un signal más que conectaremos a un elemento `` en breve. Por ahora, podemos usar este signal para crear una función que añada un elemento a nuestra lista. Recuerda, podemos actualizar el valor de un signal al asignar a su propiedad `.value`: + +```jsx +// Usaremos esto para una entrada más tarde +const text = signal(""); + +function addTodo() { + todos.value = [...todos.value, { text: text.value }]; + text.value = ""; // Limpiar valor de entrada al agregar +} +``` + +> :bulb: Consejo: Un signal sólo se actualizará si le asignas un nuevo valor. Si el valor que le asignas a un signal es igual a su valor actual, no se actualizará. +> +> ```js +> const count = signal(0); +> +> count.value = 0; // no hace nada - value ya es 0 +> +> count.value = 1; // se actualiza - value es diferente +> ``` + +Comprobemos si nuestra lógica es correcta hasta ahora. Cuando actualizamos el signal de `text` y llamamos a `addTodo()`, deberíamos ver que se añade un nuevo elemento al signal `todos`. Podemos simular este escenario al llamar directamente estas funciones - ¡No hay necesidad de una interfaz de usuario de momento! + +```jsx +// --repl +import { signal } from "@preact/signals"; + +const todos = signal([ + { text: "Comprar víveres" }, + { text: "Pasear el perro" }, +]); + +const text = signal(""); + +function addTodo() { + todos.value = [...todos.value, { text: text.value }]; + text.value = ""; // Borra el valor de la entrada al añadir +} + +// Comprueba si nuestra logica funciona +console.log(todos.value); +// Registra: [{text: "Comprar víveres"}, {text: "Pasear el perro"}] + + +// Simula añadir una nueva tarea +text.value = "Poner orden"; +addTodo(); + +// Comprueba que se ha añadido el nuevo elemento y se ha borrado el signal `text`: +console.log(todos.value); +// Registra: [{text: "Comprar víveres"}, {text: "Pasear el perro"}, {text: "Poner orden"}] + +console.log(text.value); // Registra: "" +``` + +La última característica que nos gustaría añadir es la posibilidad de eleminar un elemento de la lista. Para ello, añadiremos una función que borre un elemento dad de la lista de tareas: + +```jsx +function removeTodo(todo) { + todos.value = todos.value.filter(t => t !== todo); +} +``` + +## Construyendo la Interfaz de Usuario + +Ahora que hemos modelado el estado de nuestra aplicación, es hora de conectarla a una interfaz de usuario agradable con la que los usuarios puedan interactuar. + +```jsx +function TodoList() { + const onInput = event => (text.value = event.currentTarget.value); + + return ( + <> + + + + + ); +} +``` + +¡Y con eso ya tenemos una aplicación de tareas totalmente funcional!. Puedes probar la aplicación completa [aquí](/repl?example=todo-signals) :tada: + +## Derivación del estado mediante signals calculadas + +Vamos a añadir una característica más a nuestra aplicación de tareas: cada elemento de tareas se puede marcar como completado, y mostraremos al usuario el número de elementos que ha completado. Para ello importaremos la función [`computed(fn)`](#computedfn), que nos permite crear un nuevo signal que se calcula a partir de los valores de otros signals. El signal calculado devuelto es de sólo lectura, y su valor se actualiza automáticamente cuando cambia cualquier signal a la que se acceda desde la función callback. + +```jsx +// --repl +import { signal, computed } from "@preact/signals"; + +const todos = signal([ + { text: "Comprar víveres", completed: true }, + { text: "Pasear el perro", completed: false }, +]); + +// Crea un signal calculado a partir de otros signals +const completed = computed(() => { + // Cuando `todos` cambia, esto se ejecuta automaticamente de nuevo: + return todos.value.filter(todo => todo.completed).length; +}); + +// Registra: 1, porque una tarea es marcada como completada +console.log(completed.value); +``` + +Nuestra sencilla aplicación de lista de tareas no necesita muchos signals calculados, pero las aplicaciones más complejas tienden a confiar en computed() para evitar duplicar el estado en varios lugares. + +> :bulb: Consejo: Derivar tanto estado como sea posible garantiza que tu estado siempre tenga una única fuente de verdad. Es un principio clave de las señales. Esto hace que la depuración sea mucho más fácil en caso de que haya un fallo en la lógica de la aplicación más adelante, ya que hay menos lugares de los que preocuparse. + +## Gestión del estado global de la aplicación + +Hasta ahora, sólo hemos creado signals fuera del árbol de componentes. Esto está bien para una aplicación pequeña como una lista de tareas, pero para aplicaciones más grandes y complejas puede dificultar las pruebas. Las pruebas suelen implicar el cambio de valores en el estado de su aplicación para reproducir un determinado escenario, a continuación, pasar ese estado a los componentes y la afirmación en el HTML renderizado. Para ello, podemos extraer el estado de nuestra lista de tareas en una función: + +```jsx +function createAppState() { + const todos = signal([]); + + const completed = computed(() => { + return todos.value.filter(todo => todo.completed).length + }); + + return { todos, completed } +} +``` + +> :bulb: Consejo: Observa que conscientemente no estamos incluyendo las funciones `addTodo()` y `removeTodo(todo)` aquí. Separar los datos de las funciones que los modifican, a menudo ayuda a simplificar la arquitectura de la aplicación. Para más detalles, consulta [disdiseño orientado a datos](https://en.wikipedia.org/wiki/Data-oriented_design). + +Ahora podemos pasar nuestro estado de la aplicación de tareas como un prop al renderizar: + +```jsx +const state = createAppState(); + +// ...más adelante: + +``` + +Esto funciona en nuestra aplicación de lista de tareas porque el estado es global. Sin embargo, las aplicaciones más grandes suelen tener varios componentes que requieren acceso a los mismos fragmentos de estado. Esto suele implicar "elevar el estado" a un componente ancestro compartido. Para evitar pasar el estado mantualmente a cada componente mediante props, el estado puede ser colocado dentro de un [Context](https://preactjs.com/guide/v10/context) para que cualquier componente en el árbol pueda accerder a él. Aquí hay un ejemplo rápido de como se ve esto: + +```jsx +import { createContext } from "preact"; +import { useContext } from "preact/hooks"; +import { createAppState } from "./my-app-state"; + +const AppState = createContext(); + +render( + + + +); + +// ...más adelante cuando necesites acceso al estado de tu aplicación +function App() { + const state = useContext(AppState); + return

{state.completed}

; +} +``` + +Si quieres aprender más de como context funciona, diríge a la [documentación de Context](https://preactjs.com/guide/v10/context). + +## Estado local con signals + +La mayor parte del estado de la aplicación termina siendo pasado usando props y context. Sin embargo, hay muchos escenarios donde los componentes tienen su propio estado interno que es específico para ese componente. Puesto que no hay razón para que este estado forme parte del estado global de la lógica del negocio de la aplicación, debería limitarse al componente que lo necesita. En estos escenarios, podemos crear signals al igual que signals calculados directamente dentro del componente usando los hooks `useSignal()` y `useComputed()`: + +```jsx +import { useSignal, useComputed } from "@preact/signals"; + +function Counter() { + const count = useSignal(0); + const double = useComputed(() => count.value * 2); + + return ( +
+

{count} x 2 = {double}

+ +
+ ); +} +``` + +Estos dos hooks son finas envolturas alrededor de [`signal()`](#signalinitialvalue) y [`computed()`](#computedfn) que construyen un signal la primera vez que se ejecuta un componente, y simplemente usan ese mismo signal en las siguientes renderizaciones. + +> :bulb: Tras bambalinas, esta es la implementación: +> +> ```js +> function useSignal(value) { +> return useMemo(() => signal(value), []); +> } +> ``` + +## Uso avanzado de signals + +Los temas que hemos abarcado hasta el momento son todo lo que necesitas para ponerte en marcha. La siguiente sección está dirigida a los lectores que quieran beneficiarse aun más modelando el estado de su aplicación completamente mediante signals. + +### Reaccionar a signals externos a los componentes + +Al trabajar con signals fuera del árbol de componentes, puede que hayas notado que los signals calculados no vuelven a calcular al menos que activamente se lea su valor. Esto se debe a que los signals son perezosos por defecto: Sólo computan nuevos valores cuando se ha accedido a su valor. + +```js +const count = signal(0);Reading the value of `double` triggers it to be re-computed: +const double = computed(() => count.value * 2); + +// A pesar de actualizar el signal `count` de la que depende el signal `double`, +// `double` aún no se actualiza porque nada ha usado su valor. +count.value = 1; + +// Al leer el valor de `double`, se vuelve a calcular: +console.log(double.value); // Registra: 2 +``` + +Esto plantea una pregunta: ¿Cómo podemos suscribirnos a los signlas fuera del árbol de componentes? Quizás queramos registrar algo en la consola cada vez que el valor de un signal cambie, o persistir el estado en [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). + +Para ejecutar código arbitrario en respuesta a los cambios de signals, podemos utilizar [`effect(fn)`](https://preactjs.com/guide/v10/signals/#effectfn). De forma similar a los signals calculados, los effect rastrean a qué signals se accede y vuelven a ejecutar su callback cuando esos signals cambian. A diferencia de los signals calculados, [`effect()`](https://preactjs.com/guide/v10/signals/#effectfn) no devuelve una señal, es el final de una secuencia de cambios. + +```js +import { signal, computed, effect } from "@preact/signals"; + +const name = signal("Jane"); +const surname = signal("Doe"); +const fullName = computed(() => `${name.value} ${surname.value}`); + +// Registra el nombre cada vez que cambia. +effect(() => console.log(fullName.value)); +// Registra: "Jane Doe" + +// Actualizar `name` actualiza `fullName`, lo que activa el effect de nuevo: +name.value = "John"; +// Registra: "John Doe" +``` + +Opcionalmente, puede devolver una función de limpieza al callback proporcionado a [`effect()`](https://preactjs.com/guide/v10/signals/#effectfn) que se ejecutará antes de que se produzca la siguiente actualización. Esto le permite "limpiar" el efecto secundario y potencialmente restablecer cualquier estado para el posterios disparo del callback. + +```js +effect(() => { + Chat.connect(username.value) + + return () => Chat.disconnect(username.value) +}) +``` + +Puedes destruir un effect y desuscribir de todos los signals a las que accedió llamando la función devuelta. + +```js +import { signal, effect } from "@preact/signals"; + +const name = signal("Jane"); +const surname = signal("Doe"); +const fullName = computed(() => name.value + " " + surname.value); + +const dispose = effect(() => console.log(fullName.value)); +// Registra: "Jane Doe" + +// Destruye el effect y las suscripciones: +dispose(); + +// Actualizar `nombre` no ejecuta el effect porque ha sido eliminado. +// Tampoco recalcula `fullName` ahora que nada lo observa. +name.value = "John"; +``` + +> :bulb: Consejo: No olvides limpiar los effects si los usas mucho. De lo contrario, tu aplicación consumir más memoria de la necesaria. + + +## Leyendo signals sin suscribirse a ellos + +En las raras ocasiones en las que necesitas escribir un signal dentro de [`effect(fn)`](https://preactjs.com/guide/v10/signals/#effectfn), pero no quieras que el effect se vuelva a ejecutar cuando ese signal cambie, puedes usar `.peek()` para obtener el valor actual del signal sin suscribirte. + + +```js +const delta = signal(0); +const count = signal(0); + +effect(() => { + // Actualiza `count` sin suscribirte a `count` + count.value = count.peek() + delta.value; +}); + +// Establecer `delta` vuelve a ejecutar el effect: +delta.value = 1; + +// Esto no volverá a ejecutar el effect porque no accedio a `.value`: +count.value = 10; +``` + +> :bulb: Consejo: Los escenarios en los que no quieres suscribirte a un signal son raros. En la mayoría de los casos quieres que tu efecto se suscriba a todos los signals. Utiliza `.peek()` sólo cuando sea realmente necesario. + +Como alternativa a `.peek()`, tenemos la función [`untracked`](https://preactjs.com/guide/v10/signals/#untrackedfn) que recibe una función como argumento y devuelve el resultado de la función. En [`untracked`](https://preactjs.com/guide/v10/signals/#untrackedfn) puedes hacer referencia a cualquier signal con `.value` sin crear una suscripción. Esto puede ser útil cuando tienes una función reutilizable que accede a `.value` or necesitas acceder a más de 1 signal. + + + +```js +const delta = signal(0); +const count = signal(0); + +effect(() => { + // Actualiza `count` sin suscribirse a `count` o `delta`: + count.value = untracked(() => { + return count.value + delta.value + }); +}); +``` + + +## Combinando múltiples actualizaciones en una sola + +¿Recuerdas la función `addTodo()` que usamos anteriormente en la aplicación de tareas? Aquí esta un pequeño recordatorio de como lucía: + +```js +const todos = signal([]); +const text = signal(""); + +function addTodo() { + todos.value = [...todos.value, { text: text.value }]; + text.value = ""; +} +``` + +Observa que la función desencadena dos actualizaciones separadas: una al establecer `todos.value` y otrar cuando establecemos el valor de `.text`. Esto a veces puede ser indeseable y justificar combinar ambas actualizaciones en una, por rendimiento u otras razones. La función [`batch(fn)`](https://preactjs.com/guide/v10/signals/#batchfn) puede usarse para combinar múltiples actualizaciones de valores en una sola "asignación" al final del callback: + +```js +function addTodo() { + batch(() => { + todos.value = [...todos.value, { text: text.value }]; + text.value = ""; + }); +} +``` + +El acceso a un singal que ha sido modificada dentro de un batch reflejará su valor actualizado. El acceso al signal calculado que ha sido invalidado por otro signal dentro de un batch volverá a calcular sólo las dependencias necesarias para devolver una valor actualizado para ese signal calculado. Los demás signals invalidados no se verán afectadas y sólo se actualizarán al final del callback del batch. + +```js +// --repl +import { signal, computed, effect, batch } from "@preact/signals"; + +const count = signal(0); +const double = computed(() => count.value * 2); +const triple = computed(() => count.value * 3); + +effect(() => console.log(double.value, triple.value)); + +batch(() => { + // Establece `count`, invalidando `double` y `triple` + count.value = 1; + + // A pesar de estar en un batch, `double` refleja el nuevo valor calculado. + // Sin embargo, `triple` sólo se actualizará una vez que se complete el callback. + console.log(double.value); // Registra: 2 +}); +``` + + +> :bulb: Consejo: Los batches se pueden anidar, en cuyo caso las actualizaciones se vierten sólo despues de que se haya completado el callback del batch más externo. + + +### Optimizaciones de renderizado + +Con los signals podemos evitar el renderizado del Virtual DOM y vincular los cambios del signal directamente a las mutaciones del DOM. Si pasas un signal a JSX en una posición de texto, se va a renderizar como un texto y se actualizará automáticamente en el lugar sin la difusión del Virtual DOM: + +```jsx +const count = signal(0); + +function Unoptimized() { + // Vuelve a renderizar el componente cuando `count` cambia: + return

{count.value}

; +} + +function Optimized() { + // El texto automaticamente cambia sin volver a renderizar el componente: + return

{count}

; +} +``` + +Para habilitar esta optimización, pase el signal a JSX en lugar de acceder a su propieda `.value`. + +Una optimización de renderizado similar también es soportada cuando se pasan signals como props en elementos DOM. + +## API + +Esta sección es una visión general de la API. Pretende ser una referencia rápida para quienes ya saben utilizar los signals y necesitan un recordatorio de lo que está disponible. + +### signal(initialValue) + +Crea un nuevo signal con el argumento dado como su valor inicial: + +```js +const count = signal(0); +``` + +Al crear signals dentro de un componente, utilice la variante del hook: `useSignal(initialValue)`. + +El signal devuleto tienen una propiedad `.value` que puede ser get o set para leer o escribir su valor. Para leer un signal sin suscribirse a ella, utilice `signal.peek()`. + +### computed(fn) + +Crea un nuevo signal que es calculada basado en los valores de otros signals. El signal calculado es de sólo lectura, y su valor se actualiza automáticamente cuando cambia cualquier signal a la que se acceda desde la función callback. + +```js +const name = signal("Jane"); +const surname = signal("Doe"); + +const fullName = computed(() => `${name.value} ${surname.value}`); +``` + +Al crear un signal calculado dentro de un componente, utilice la variante del hook: `useComputed(fn)`. + +### effect(fn) + +Para ejecutar código arbitario en respuesta a los cambios de signal, podemos utilizar `effect(fn)`. De forma similar a los signals calculados, los effects rastrean a qué signals se accede y vuelven a ejecutar su callback cuando esos signals cambian. Si el callback devuelve una función, ésta se ejecutará antes de la siguiente actualización del valor. A diferencia de los signals calculados, `effect()` no devuelve un signal, ese el final de una secuencia de cambios. + +```js +const name = signal("Jane"); + +// Registra a la consola cuando `name` cambia: +effect(() => console.log('Hola', name.value)); +// Registra: "Hola Jane" + +name.value = "John"; +// Registra: "Hola John" +``` + +Al responder a los cambios de signal dentro de un componente, utilice la variente del hook: `useSignalEffect(fn)`. + +### batch(fn) + +La funcion `batch(fn)` puede ser usado para combinar varias actualizaciones de valor en una sola "asignación" al final del callback proporcionado. Los batches pueden anidarse y los cambios sólo se envían una vez que finaliza el callback más externo. El acceso a un signal que ha sido modificado dentro un batch reflejará su valor actualizado. + +```js +const name = signal("Jane"); +const surname = signal("Doe"); + +// Combina ambas escrituras en una sola actualización +batch(() => { + name.value = "John"; + surname.value = "Smith"; +}); +``` + +### untracked(fn) + +La función `untracked(fn)` puede ser usado para acceder al valor de varios signals sin suscribirse a ellos. + +```js +const name = signal("Jane"); +const surname = signal("Doe"); + +effect(() => { + untracked(() => { + console.log(`${name.value} ${surname.value}`) + }) +}) +``` diff --git a/src/assets/contributors.json b/src/assets/contributors.json index 0ad84e6ae..1a871f39c 100644 --- a/src/assets/contributors.json +++ b/src/assets/contributors.json @@ -1,282 +1,283 @@ [ - "marvinhagemeister", - "developit", - "andrewiggins", - "JoviDeCroock", - "rschristian", - "robertknight", - "cristianbote", - "ForsakenHarmony", - "prateekbh", - "jviide", - "AlexGalays", - "rpetrich", - "sventschui", - "k1r0s", - "fekete965", - "38elements", - "NekR", - "mochiya98", - "pmkroeker", - "mkxml", - "wardpeet", - "shoonia", - "AimWhy", - "valotas", - "tanhauhau", - "braddunbar", - "KevinDoughty", - "reznord", - "kristoferbaxter", - "gpoitch", - "natevw", - "triallax", - "egdbear", - "jmrog", - "samsam-ahmadi", - "Rafi993", - "vutran", - "billneff79", - "calebeby", - "zouhir", - "yaodingyd", - "lilnasy", - "Download", - "Scott-Fischer", - "harish2704", - "garybernhardt", - "Alexendoo", - "JiLiZART", - "kitten", - "sangupta", - "staeke", - "yuqianma", - "paranoidjk", - "ouzhenkun", - "Austaras", - "Connormiha", - "craftedsystems", - "Almo7aya", - "aralroca", - "btm6084", - "btk5h", - "gcraftyg", - "guybedford", - "hadeeb", - "ngyikp", - "lukeed", - "LukasBombach", - "jridgewell", - "programbo", - "johakr", - "gengjiawen", - "jakearchibald", - "futantan", - "teodragovic", - "niedzielski", - "timgates42", - "siddharthkp", - "utkarshkukreti", - "vaneenige", - "boarwell", - "sebastiandotdev", - "deadem", - "intrnl", - "juicelink", - "mseddon", - "polemius", - "rmacklin", - "wojtczal", - "cbbfcd", - "3846masa", - "asolove", - "danielbayerlein", - "jrf0110", - "RRDAWLX", - "ddayguerrero", - "jamesb3ll", - "jramanat-oracle", - "kuronijin", - "ivantm", - "marconi1992", - "cmlenz", - "Boshen", - "billti", - "bmeurer", - "4cm4k1", - "andybons", - "amilajack", - "impronunciable", - "zalishchuk", - "btd", - "DonIsaac", - "feross", - "ilogico", - "Popovkov57", - "PodaruDragos", - "mitranim", - "namankheterpal", - "kruczy", - "MicahZoltu", - "maxbrieiev", - "matthewp", - "Verseth", - "ianobermiller", - "jackmoore", - "scurker", - "jeremy-coleman", - "jdanford", - "jmfirth", - "9renpoto", - "wildlyinaccurate", - "squidfunk", - "shinyama-k", - "mbrukman", - "mikestead", - "Geo25rey", - "kaisermann", - "ctrlplusb", - "z11h", - "AlexanderOtavka", - "toniopelo", - "firatsarlar", - "hikouki", - "david-nordvall", - "vpzomtrrfrt", - "vitormalencar", - "lcxfs1991", - "hassanbazzi", - "dragomano", - "joaolucasl", - "rosskhanas", - "ddprrt", - "huruji", - "pazguille", - "helloworld-hellohyeon", - "addyosmani", - "chakrakan", - "n0hack", - "tao1991123", - "blenderskool", - "ZUR1C4T0", - "David-zzg", - "Nayejun", - "alexkrolick", - "rykdesjardins", - "ieeah", - "Worble", - "shaedrich", - "yhau1989", - "rcowsill", - "mikekidder", - "Marabyte", - "dandv", - "sapegin", - "TechQuery", - "darvi-sh", - "ryuyz", - "digitalica", - "pradeepb6", - "processprocess", - "Nicolas-Orozco", - "mozmorris", - "ofgo", - "tinymachine", - "ttntm", - "Vrq", - "kuldeepkeshwar", - "mikaturk", - "nbyfleet", - "malcolmyu", - "pl12133", - "thawkin3", - "BartWaardenburg", - "Anwardo", - "Otto-AA", - "KiritaniAyaka", - "Byacrya", - "guaiamum", - "masto", - "belohlavek", - "Duske", - "Ende93", - "kidonng", - "Khaledgarbaya", - "joeldenning", - "EthanStandel", - "fabian-hiller", - "montogeek", - "jsejcksn", - "Jinex2012", - "gabrielcoronel", - "bspaulding", - "janbiasi", - "bhollis", - "stevenle", - "ooade", - "shelacek", - "zgoda", - "kolodziejczakM", - "PuruVJ", - "TimDaub", - "rkostrzewski", - "jgierer12", - "psabharwal123", - "harshitkumar31", - "StephanBijzitter", - "jonathantneal", - "jamesgeorge007", - "thangngoc89", - "SaraVieira", - "knight-bubble", - "DenysVuika", - "VanTanev", - "johnhaitas", - "zubhav", - "lyubomir-bozhinov", - "lwakefield", - "kentr", - "framp", - "davi-mbatista", - "dependabot-support", - "kevinweber", - "Akiyamka", - "pimdewit", - "heithemmoumni", - "bz2", - "jpnelson", - "SolarLiner", - "einSelbst", - "fisker", - "MichaelDeBoey", - "watsonarw", - "jnpwebdeveloper", - "seroy", - "yyx990803", - "FredKSchott", - "PepsRyuu", - "piotr-cz", - "takurinton", - "EmilTholin", - "matiasperz", - "jbt", - "wilberforce", - "dbetteridge", - "michael-erskine", - "AjayPoshak", - "ElMassimo", - "liamdon", - "sapphi-red", - "epiqueras", - "leerob", - "eddyw", - "CodyJasonBennett", - "XantreDev", - "billybimbob", - "nichoth", - "elliotwaite", - "Xstoudi", - "dcporter", - "prinsss", - "sand4rt" + "marvinhagemeister", + "developit", + "andrewiggins", + "JoviDeCroock", + "rschristian", + "robertknight", + "cristianbote", + "ForsakenHarmony", + "prateekbh", + "jviide", + "AlexGalays", + "rpetrich", + "sventschui", + "k1r0s", + "fekete965", + "38elements", + "NekR", + "mochiya98", + "pmkroeker", + "mkxml", + "wardpeet", + "shoonia", + "AimWhy", + "valotas", + "tanhauhau", + "braddunbar", + "KevinDoughty", + "reznord", + "kristoferbaxter", + "gpoitch", + "natevw", + "triallax", + "egdbear", + "jmrog", + "samsam-ahmadi", + "Rafi993", + "vutran", + "billneff79", + "calebeby", + "zouhir", + "yaodingyd", + "lilnasy", + "Download", + "Scott-Fischer", + "harish2704", + "garybernhardt", + "Alexendoo", + "JiLiZART", + "kitten", + "sangupta", + "staeke", + "yuqianma", + "paranoidjk", + "ouzhenkun", + "Austaras", + "Connormiha", + "craftedsystems", + "Almo7aya", + "aralroca", + "btm6084", + "btk5h", + "gcraftyg", + "guybedford", + "hadeeb", + "ngyikp", + "lukeed", + "LukasBombach", + "jridgewell", + "programbo", + "johakr", + "gengjiawen", + "jakearchibald", + "futantan", + "teodragovic", + "niedzielski", + "timgates42", + "siddharthkp", + "utkarshkukreti", + "vaneenige", + "boarwell", + "sebastiandotdev", + "deadem", + "intrnl", + "juicelink", + "mseddon", + "polemius", + "rmacklin", + "wojtczal", + "cbbfcd", + "3846masa", + "asolove", + "danielbayerlein", + "jrf0110", + "RRDAWLX", + "ddayguerrero", + "jamesb3ll", + "jramanat-oracle", + "kuronijin", + "ivantm", + "marconi1992", + "cmlenz", + "Boshen", + "billti", + "bmeurer", + "4cm4k1", + "andybons", + "amilajack", + "impronunciable", + "zalishchuk", + "btd", + "DonIsaac", + "feross", + "ilogico", + "Popovkov57", + "PodaruDragos", + "mitranim", + "namankheterpal", + "kruczy", + "MicahZoltu", + "maxbrieiev", + "matthewp", + "Verseth", + "ianobermiller", + "jackmoore", + "scurker", + "jeremy-coleman", + "jdanford", + "jmfirth", + "9renpoto", + "wildlyinaccurate", + "squidfunk", + "shinyama-k", + "mbrukman", + "mikestead", + "Geo25rey", + "kaisermann", + "ctrlplusb", + "z11h", + "AlexanderOtavka", + "toniopelo", + "firatsarlar", + "hikouki", + "david-nordvall", + "vpzomtrrfrt", + "vitormalencar", + "lcxfs1991", + "hassanbazzi", + "dragomano", + "joaolucasl", + "rosskhanas", + "ddprrt", + "huruji", + "pazguille", + "helloworld-hellohyeon", + "addyosmani", + "chakrakan", + "n0hack", + "tao1991123", + "blenderskool", + "ZUR1C4T0", + "David-zzg", + "Nayejun", + "alexkrolick", + "rykdesjardins", + "ieeah", + "Worble", + "shaedrich", + "yhau1989", + "rcowsill", + "mikekidder", + "Marabyte", + "dandv", + "sapegin", + "TechQuery", + "darvi-sh", + "ryuyz", + "digitalica", + "pradeepb6", + "processprocess", + "Nicolas-Orozco", + "mozmorris", + "ofgo", + "tinymachine", + "ttntm", + "Vrq", + "kuldeepkeshwar", + "mikaturk", + "nbyfleet", + "malcolmyu", + "pl12133", + "thawkin3", + "BartWaardenburg", + "Anwardo", + "Otto-AA", + "KiritaniAyaka", + "Byacrya", + "guaiamum", + "masto", + "belohlavek", + "Duske", + "Ende93", + "kidonng", + "Khaledgarbaya", + "joeldenning", + "EthanStandel", + "fabian-hiller", + "montogeek", + "jsejcksn", + "Jinex2012", + "gabrielcoronel", + "bspaulding", + "janbiasi", + "bhollis", + "stevenle", + "ooade", + "shelacek", + "zgoda", + "kolodziejczakM", + "PuruVJ", + "TimDaub", + "rkostrzewski", + "jgierer12", + "psabharwal123", + "harshitkumar31", + "StephanBijzitter", + "jonathantneal", + "jamesgeorge007", + "thangngoc89", + "SaraVieira", + "knight-bubble", + "DenysVuika", + "VanTanev", + "johnhaitas", + "zubhav", + "lyubomir-bozhinov", + "lwakefield", + "kentr", + "framp", + "davi-mbatista", + "dependabot-support", + "kevinweber", + "Akiyamka", + "pimdewit", + "heithemmoumni", + "bz2", + "jpnelson", + "SolarLiner", + "einSelbst", + "fisker", + "MichaelDeBoey", + "watsonarw", + "jnpwebdeveloper", + "seroy", + "yyx990803", + "FredKSchott", + "PepsRyuu", + "piotr-cz", + "takurinton", + "EmilTholin", + "matiasperz", + "jbt", + "wilberforce", + "dbetteridge", + "michael-erskine", + "AjayPoshak", + "ElMassimo", + "liamdon", + "sapphi-red", + "epiqueras", + "leerob", + "eddyw", + "CodyJasonBennett", + "XantreDev", + "billybimbob", + "nichoth", + "elliotwaite", + "Xstoudi", + "dcporter", + "prinsss", + "sand4rt", + "EGAMAGZ" ] diff --git a/src/config.json b/src/config.json index 18a03b215..207afcc7f 100644 --- a/src/config.json +++ b/src/config.json @@ -59,11 +59,13 @@ "help": { "en": "Help", "kr": "정답 확인", - "ru": "Помощь" + "ru": "Помощь", + "es": "Ayuda" }, "solve": { "en": "Solve", - "ru": "Решить" + "ru": "Решить", + "es": "Resolver" } } }, @@ -86,6 +88,7 @@ "href": "/tutorial", "name": { "en": "Tutorial", + "es": "Tutorial", "zh": "教程", "kr": "튜토리얼", "ru": "Учебник" @@ -112,7 +115,7 @@ "de": "Mehr", "pt-br": "Sobre", "ja": "Preactについて", - "es": "Sobre", + "es": "Acerca de", "zh": "关于", "kr": "소개", "ru": "Разное" @@ -190,6 +193,7 @@ "path": "/blog", "name": { "en": "Blog", + "es": "Blog", "zh": "博客", "kr": "블로그", "ru": "Блог" @@ -199,6 +203,7 @@ "path": "/repl", "name": { "en": "REPL", + "es": "REPL", "zh": "在线试用" } } @@ -208,6 +213,7 @@ { "name": { "en": "Introduction", + "es": "Introducción", "de": "Einführung", "zh": "介绍", "kr": "개요", @@ -223,7 +229,8 @@ "de": "Los geht's", "zh": "开始上手", "kr": "시작하기", - "ru": "Первые шаги" + "ru": "Первые шаги", + "es": "Para empezar" } }, { @@ -234,7 +241,8 @@ "pt-br": "o que há de novo", "zh": "新鲜功能", "kr": "새로운 기능", - "ru": "Что нового?" + "ru": "Что нового?", + "es": "¿Qué hay de nuevo?" } }, { @@ -246,7 +254,8 @@ "de": "Migration von 8.x", "zh": "从 8.x 版本更新", "kr": "8.x에서 업그레이드하기", - "ru": "Обновление с 8.x" + "ru": "Обновление с 8.x", + "es": "Actualizar desde 8.x" } }, { @@ -258,7 +267,8 @@ "de": "Tutorial", "zh": "教程", "kr": "튜토리얼", - "ru": "Учебник" + "ru": "Учебник", + "es": "Tutorial" } } ] @@ -268,7 +278,8 @@ "en": "Essentials", "zh": "基础", "kr": "필수 항목", - "ru": "Основы" + "ru": "Основы", + "es": "Esenciales" }, "routes": [ { @@ -280,7 +291,8 @@ "de": "Komponenten", "zh": "组件", "kr": "컴포넌트", - "ru": "Компоненты" + "ru": "Компоненты", + "es": "Componentes" } }, { @@ -292,7 +304,8 @@ "de": "Hooks", "zh": "钩子", "kr": "훅", - "ru": "Хуки" + "ru": "Хуки", + "es": "Hooks" } }, { @@ -313,7 +326,8 @@ "de": "Formulare", "zh": "表单", "kr": "폼", - "ru": "Формы" + "ru": "Формы", + "es": "Formularios" } }, { @@ -325,7 +339,8 @@ "de": "Referenzen", "zh": "引用", "kr": "레퍼런스", - "ru": "Рефы" + "ru": "Рефы", + "es": "Referencias" } }, { @@ -337,7 +352,8 @@ "de": "Kontext", "zh": "上下文", "kr": "컨텍스트", - "ru": "Контекст" + "ru": "Контекст", + "es": "Contexto" } } ] @@ -347,7 +363,8 @@ "en": "Debug & Test", "zh": "调试与测试", "kr": "디버그 & 테스트", - "ru": "Отладка и тестирование" + "ru": "Отладка и тестирование", + "es": "Depuración y Pruebas" }, "routes": [ { @@ -359,7 +376,8 @@ "de": "Entwickler-Tools", "zh": "调试工具", "kr": "디버깅 도구", - "ru": "Инструменты отладки" + "ru": "Инструменты отладки", + "es": "Heramientas de depuración" } }, { @@ -371,7 +389,8 @@ "de": "Preact Testing Library", "zh": "Preact 测试库", "kr": "Preact 테스팅 라이브러리", - "ru": "Preact Testing Library" + "ru": "Preact Testing Library", + "es": "Librería de pruebas de Preact" } }, { @@ -383,7 +402,8 @@ "de": "Unit-Tests mit Enzyme", "kr": "Enzyme를 이용한 유닛 테스트", "zh": "使用 Enzyme 进行单元测试", - "ru": "Модульное тестирование с помощью Enzyme" + "ru": "Модульное тестирование с помощью Enzyme", + "es": "Pruebas unitarias con Enzyme" } } ] @@ -393,7 +413,8 @@ "en": "React compatibility", "zh": "React 兼容性", "kr": "React 호환성", - "ru": "Совместимость с React" + "ru": "Совместимость с React", + "es": "Compatibilidad con React" }, "routes": [ { @@ -405,7 +426,8 @@ "de": "Unterschiede zu React", "kr": "React와의 차이점", "zh": "与 React 的区别", - "ru": "Отличия от React" + "ru": "Отличия от React", + "es": "Diferencias con React" } }, { @@ -417,7 +439,8 @@ "de": "Wechsel zu Preact", "kr": "Preact로 전환", "zh": "从 React 转到 Preact", - "ru": "Переход на Preact" + "ru": "Переход на Preact", + "es": "Cambiando a Preact" } } ] @@ -428,7 +451,8 @@ "de": "Fortgeschritten", "zh": "进阶", "kr": "고급", - "ru": "Дополнительно" + "ru": "Дополнительно", + "es": "Avanzado" }, "routes": [ { @@ -440,7 +464,8 @@ "de": "API Referenz", "zh": "API 参考", "kr": "API 참조", - "ru": "Справочник по API" + "ru": "Справочник по API", + "es": "Referencias de la API" } }, { @@ -452,7 +477,8 @@ "de": "Web Components", "zh": "Web 组件", "kr": "웹 컴포넌트", - "ru": "Веб-компоненты" + "ru": "Веб-компоненты", + "es": "Web Components" } }, { @@ -464,7 +490,21 @@ "de": "Server-Side Rendering", "zh": "服务端渲染", "kr": "서버 측 렌더링", - "ru": "Рендеринг на стороне сервера" + "ru": "Рендеринг на стороне сервера", + "es": "Renderizado del lado del servidor (SSR)" + } + }, + { + "path": "/external-dom-mutations", + "name": { + "en": "External DOM Mutations", + "pt-br": "Mutações do DOM externas", + "ja": "Preactのステート管理の範囲外のDOMとの連携", + "de": "Externe DOM Mutationen", + "zh": "外部 DOM 修改", + "kr": "외부 DOM 변경", + "ru": "Внешние мутации DOM", + "es": "Mutaciones externas del DOM" } }, { @@ -475,7 +515,8 @@ "de": "Optionen", "zh": "选项钩子", "kr": "옵션 훅", - "ru": "Опционные хуки" + "ru": "Опционные хуки", + "es": "Opciones de Hooks" } }, { @@ -486,7 +527,8 @@ "path": "/no-build-workflows", "name": { "en": "No-Build Workflows", - "zh": "无构建工具工作流" + "zh": "无构建工具工作流", + "es": "Workflows sin construcción" } } ] @@ -500,7 +542,8 @@ "pt-br": "Começando", "de": "Los geht's", "zh": "开始上手", - "ru": "Первые шаги" + "ru": "Первые шаги", + "es": "Para empezar" } }, { @@ -510,7 +553,8 @@ "pt-br": "Diferenças do React", "de": "Unterschiede zu React", "zh": "与 React 的区别", - "ru": "Отличия от React" + "ru": "Отличия от React", + "es": "Diferencias con React" } }, { @@ -520,7 +564,8 @@ "pt-br": "Mudando para Preact", "de": "Wechsel zu Preact", "zh": "从 React 转到 Preact", - "ru": "Переход на Preact" + "ru": "Переход на Preact", + "es": "Cambiando a Preact" } }, { @@ -530,7 +575,8 @@ "pt-br": "Tipos de Componentes", "de": "Komponenten-Typen", "zh": "组件类型", - "ru": "Типы компонентов" + "ru": "Типы компонентов", + "es": "Tipos de Componentes" } }, { @@ -540,7 +586,8 @@ "pt-br": "Referência da API", "de": "API Referenz", "zh": "API 参考", - "ru": "Справочник по API" + "ru": "Справочник по API", + "es": "Referencias de la API" } }, { @@ -550,7 +597,8 @@ "pt-br": "Formulários", "de": "Formulare", "zh": "表单", - "ru": "Формы" + "ru": "Формы", + "es": "Formularios" } }, { @@ -558,7 +606,8 @@ "name": { "en": "Linked State", "zh": "关联状态", - "ru": "Связанное состояние" + "ru": "Связанное состояние", + "es": "Estado vinculado" } }, { @@ -568,7 +617,8 @@ "pt-br": "Mutações do DOM externas", "de": "Externe DOM Mutationen", "zh": "外部 DOM 修改", - "ru": "Внешние мутации DOM" + "ru": "Внешние мутации DOM", + "es": "Mutaciones externas del DOM" } }, { @@ -578,7 +628,8 @@ "pt-br": "Extendendo Componentes", "de": "Komponente Erweitern", "zh": "扩展组件", - "ru": "Расширение компонентов" + "ru": "Расширение компонентов", + "es": "Extender Componente" } }, { @@ -588,7 +639,8 @@ "pt-br": "Teste unitado com Enzyme", "de": "Unit-Tests mit Enzyme", "zh": "使用 Enzyme 进行单元测试", - "ru": "Модульное тестирование с помощью Enzyme" + "ru": "Модульное тестирование с помощью Enzyme", + "es": "Pruebas unitarias con Enzyme" } }, { @@ -596,7 +648,8 @@ "name": { "en": "Progressive Web Apps", "zh": "渐进式 Web 应用", - "ru": "Прогрессивные веб-приложения" + "ru": "Прогрессивные веб-приложения", + "es": "Aplicaciones Web Progresivas" } } ] @@ -637,11 +690,13 @@ "date": "2024-08-06", "name": { "en": "Build your own Island Components", - "ru": "Создание собственных островных компонентов" + "ru": "Создание собственных островных компонентов", + "es": "Construye tus propios componentes de isla" }, "excerpt": { "en": "Demystifying how island architechture works and being able to setup your own, using tools you already have around you.", - "ru": "Вы узнаете, как работает островная архитектура, и сможете создать свою собственную, используя инструменты, которые уже есть у вас под рукой." + "ru": "Вы узнаете, как работает островная архитектура, и сможете создать свою собственную, используя инструменты, которые уже есть у вас под рукой.", + "es": "Desmitificando cómo funciona la arquitectura de islas y poder configurar la tuya propia, utilizando las herramientas que ya tienes a tu alrededor." } }, { @@ -649,11 +704,13 @@ "date": "2024-08-06", "name": { "en": "Prerendering with Preset Vite", - "ru": "Предварительный рендеринг с пресетом Vite" + "ru": "Предварительный рендеринг с пресетом Vite", + "es": "Prerenderizado con Preset Vite" }, "excerpt": { "en": "It's been a half-year since our prerendering plugin has somewhat quietly become available in `@preact/preset-vite`, so let's talk about it, our history, and the ecosystem at large", - "ru": "Прошло полгода с тех пор, как наш плагин для пререндеринга стал доступен в `@preact/preset-vite`, так что давайте поговорим о нем, о нашей истории и об экосистеме в целом." + "ru": "Прошло полгода с тех пор, как наш плагин для пререндеринга стал доступен в `@preact/preset-vite`, так что давайте поговорим о нем, о нашей истории и об экосистеме в целом.", + "es": "Hace medio año que nuestro plugin de preprocesamiento está disponible en `@preact/preset-vite`, así que hablemos de él, de nuestra historia y del ecosistema en general." } }, { @@ -661,11 +718,13 @@ "date": "2024-05-24", "name": { "en": "Preact X, a story of stability", - "ru": "Preact X, история стабильности" + "ru": "Preact X, история стабильности", + "es": "Preact X, una historia de estabilidad" }, "excerpt": { "en": "Preact X has been released for five years, let's go over all the exciting things that have happened.", - "ru": "Preact X выпускается уже пять лет, давайте вспомним всё, что произошло за это время." + "ru": "Preact X выпускается уже пять лет, давайте вспомним всё, что произошло за это время.", + "es": "Preact X ha sido lanzado durante cinco años, repasemos todas las cosas emocionantes que han sucedido." } }, { @@ -709,67 +768,78 @@ { "path": "/tutorial", "name": { - "en": "Learn Preact" + "en": "Learn Preact", + "es": "Aprende Preact" } }, { "path": "/tutorial/01-vdom", "name": { - "en": "Virtual DOM" + "en": "Virtual DOM", + "es": "DOM Virtual" } }, { "path": "/tutorial/02-events", "name": { - "en": "Events" + "en": "Events", + "es": "Eventos" } }, { "path": "/tutorial/03-components", "name": { - "en": "Components" + "en": "Components", + "es": "Componentes" } }, { "path": "/tutorial/04-state", "name": { - "en": "State" + "en": "State", + "es": "Estado" } }, { "path": "/tutorial/05-refs", "name": { - "en": "Refs" + "en": "Refs", + "es": "Referencias (Refs)" } }, { "path": "/tutorial/06-context", "name": { - "en": "Context" + "en": "Context", + "es": "Contexto" } }, { "path": "/tutorial/07-side-effects", "name": { - "en": "Side Effects" + "en": "Side Effects", + "es": "Efectos Secundarios" } }, { "path": "/tutorial/08-keys", "name": { - "en": "Keys" + "en": "Keys", + "es": "Claves" } }, { "path": "/tutorial/09-error-handling", "name": { - "en": "Error Handling" + "en": "Error Handling", + "es": "Manejo de Errores" } }, { "path": "/tutorial/10-links", "name": { - "en": "Congratulations!" + "en": "Congratulations!", + "es": "¡Felicidades!" } } ],