diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 1f820b0c..c3bad3d0 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -67,12 +67,18 @@ services: proxy: image: mitmproxy/mitmproxy - command: mitmweb --mode regular@8888 -s /data/mitmproxy-local.py -v --web-host 0.0.0.0 -k --set tls_version_client_min=UNBOUNDED --set tls_version_server_min=UNBOUNDED --set web_password=letmein + command: > + mitmweb -v --mode regular@8888 + -s /data/mitmproxy-local.py + --web-host 0.0.0.0 --set web_password=letmein + -k --set tls_version_client_min=UNBOUNDED --set tls_version_server_min=UNBOUNDED + --set key_size=1024 ports: - 8888:8888 - 127.0.0.1:8081:8081 volumes: - "./mitmproxy-local.py:/data/mitmproxy-local.py" + - "./mitmproxy-data/:/home/mitmproxy/.mitmproxy/" extra_hosts: - "host.docker.internal:host-gateway" diff --git a/.gitignore b/.gitignore index 75f4be01..13e00578 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist/ newman config.json uploads/ + +.docker/mitmproxy-data \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 8c250e4a..06018eff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,13 @@ "env": { "PN_JUXTAPOSITION_UI_USE_PRESETS": "docker" } + }, + { + "type": "node-terminal", + "name": "Rebuild gRPC", + "request": "launch", + "command": "npm rebuild @repo/grpc-client", + "cwd": "${workspaceFolder}" } ], "compounds": [ diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 863970b2..e9d6d198 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -27,6 +27,7 @@ "ejs": "^3.1.10", "esbuild-plugin-copy": "^2.1.1", "express": "^4.21.2", + "express-async-errors": "^3.1.1", "express-prom-bundle": "^7.0.2", "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", @@ -52,7 +53,8 @@ "redis": "^4.7.0", "sharp": "^0.33.5", "tga": "^1.0.7", - "tsx": "^4.19.3" + "tsx": "^4.19.3", + "zod": "^3.25.56" }, "devDependencies": { "@pretendonetwork/eslint-config": "^0.0.11", diff --git a/apps/juxtaposition-ui/src/api/post.ts b/apps/juxtaposition-ui/src/api/post.ts new file mode 100644 index 00000000..ec248fb4 --- /dev/null +++ b/apps/juxtaposition-ui/src/api/post.ts @@ -0,0 +1,65 @@ +import { apiFetchUser } from '@/fetch'; +import type { UserTokens } from '@/fetch'; + +/* !!! HEY + * This type lives in apps/miiverse-api/src/services/internal/contract/post.ts + * Modify it there and copy-paste here! */ + +/* This type is the contract for the frontend. If we make changes to the db, this shape should be kept. */ +export type PostDto = { + id: string; + title_id?: string; // number + screen_name: string; + body: string; + app_data?: string; // nintendo base64 + + painting?: string; // base64 or '', undef for PMs + screenshot?: string; // URL frag (leading /) or '', undef for PMs + screenshot_length?: number; + + search_key?: string[]; // can be [] + topic_tag?: string; // can be '' + + community_id: string; // number + created_at: string; // ISO Z + feeling_id?: number; + + is_autopost: boolean; + is_community_private_autopost: boolean; + is_spoiler: boolean; + is_app_jumpable: boolean; + + empathy_count: number; + country_id: number; + language_id: number; + + mii: string; // nintendo base64 + mii_face_url: string; // full URL (cdn., r2-cdn.) + + pid: number; + platform_id?: number; + region_id?: number; + parent: string | null; + + reply_count: number; + verified: boolean; + + message_to_pid: string | null; + removed: boolean; + removed_by?: number; + removed_at?: string; // ISO Z + removed_reason?: string; + + yeahs: number[]; +}; + +/** + * Fetches a Post for a given ID. + * @param tokens User to perform fetch as. Responses will be according to this users' permissions (user, moderator etc.) + * @param post_id The ID of the post to get. + * @returns Post object + */ +export async function getPostById(tokens: UserTokens, post_id: string): Promise { + const post = await apiFetchUser(tokens, `/api/v1/posts/${post_id}`); + return post; +} diff --git a/apps/juxtaposition-ui/src/config.ts b/apps/juxtaposition-ui/src/config.ts index 4bc415b3..e7d99f8e 100644 --- a/apps/juxtaposition-ui/src/config.ts +++ b/apps/juxtaposition-ui/src/config.ts @@ -9,6 +9,8 @@ const schema = z.object({ logSensitive: zodCoercedBoolean().default(false), httpPort: z.coerce.number().default(8080), httpCookieDomain: z.string().default('.pretendo.network'), + /** "Safe" base origin for login-redirect validation */ + httpBaseUrl: z.string().default('https://juxt.pretendo.network'), /** Configures proxy trust (X-Forwarded-For etc.). Can be `true` to unconditionally trust, or * provide a numeric hop count, or comma-seperated CIDR ranges. * See https://expressjs.com/en/guide/behind-proxies.html @@ -94,7 +96,8 @@ export const config = { http: { port: unmappedConfig.httpPort, cookieDomain: unmappedConfig.httpCookieDomain, - trustProxy: unmappedConfig.httpTrustProxy + trustProxy: unmappedConfig.httpTrustProxy, + baseUrl: unmappedConfig.httpBaseUrl }, metrics: { enabled: unmappedConfig.metricsEnabled, diff --git a/apps/juxtaposition-ui/src/fetch.ts b/apps/juxtaposition-ui/src/fetch.ts index e910bf50..cac0eb42 100644 --- a/apps/juxtaposition-ui/src/fetch.ts +++ b/apps/juxtaposition-ui/src/fetch.ts @@ -1,18 +1,32 @@ import { Metadata } from 'nice-grpc'; import { config } from '@/config'; import { grpcClient } from '@/grpc'; +import type { PacketResponse } from '@repo/grpc-client/out/packet'; export type FetchOptions = { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; - headers?: Record; + headers?: Record; body?: Record | undefined | null; }; +export interface FetchError extends Error { + name: 'FetchError'; + status: number; + response: any; +} +function FetchError(response: PacketResponse, message: string): FetchError { + const error = new Error(message) as FetchError; + error.name = 'FetchError'; + error.status = response.status; + error.response = response.payload; // parse json? + return error; +} + function isErrorHttpStatus(status: number): boolean { return status >= 400 && status < 600; } -export async function apiFetch(path: string, options?: FetchOptions): Promise { +export async function apiFetch(path: string, options?: FetchOptions): Promise { const defaultedOptions = { method: 'GET', headers: {}, @@ -20,20 +34,39 @@ export async function apiFetch(path: string, options?: FetchOptions): Promise }; const metadata = Metadata({ - ...defaultedOptions.headers, 'X-API-Key': config.grpc.miiverse.apiKey }); const response = await grpcClient.sendPacket({ path, method: defaultedOptions.method, + headers: JSON.stringify(defaultedOptions.headers), payload: defaultedOptions.body ? JSON.stringify(defaultedOptions.body) : undefined }, { metadata }); - if (isErrorHttpStatus(response.status)) { - throw new Error(`HTTP error! status: ${response.status}`); + if (response.status === 404) { + return null; + } else if (isErrorHttpStatus(response.status)) { + throw FetchError(response, `HTTP error! status: ${response.status} ${response.payload}`); } return JSON.parse(response.payload) as T; } + +export type UserTokens = { + serviceToken?: string; + oauthToken?: string; +}; + +export async function apiFetchUser(tokens: UserTokens, path: string, options?: FetchOptions): Promise { + options = { + ...options, + headers: { + 'x-service-token': tokens.serviceToken, + 'x-oauth-token': tokens.oauthToken, + ...options?.headers + } + }; + return apiFetch(path, options); +} diff --git a/apps/juxtaposition-ui/src/images.ts b/apps/juxtaposition-ui/src/images.ts new file mode 100644 index 00000000..e2f87133 --- /dev/null +++ b/apps/juxtaposition-ui/src/images.ts @@ -0,0 +1,129 @@ +import { Buffer } from 'node:buffer'; +// @ts-expect-error Missing upstream types for this library +import TGA from 'tga'; +// @ts-expect-error Missing upstream types for this library +import imagePixels from 'image-pixels'; +import { inflate, deflate } from 'pako'; +import { PNG } from 'pngjs'; +import bmp from 'bmp-js'; +import sharp from 'sharp'; +import { logger } from '@/logger'; + +/** Ingests a BMP-format painting and converts it to the usual TGA format. + * @param painting base64-encoded bmp image + * @returns base64-encoded zlib-compressed TGA + */ +export async function processBmpPainting(painting: string): Promise { + const paintingBuffer = Buffer.from(painting, 'base64'); + const bitmap = bmp.decode(paintingBuffer); + const tga = createTgaFromPixelData(bitmap.width, bitmap.height, bitmap.data, false); + + let output: Uint8Array; + try { + output = deflate(tga, { level: 6 }); + } catch (err) { + logger.error(err, 'Could not compress painting'); + return null; + } + return Buffer.from(output.buffer).toString('base64'); +} + +/** + * Ingests a raw painting and converts it for CDN upload. + * @param painting base64-encoded zlib-compressed TGA + * @returns PNG in buffer + */ +export async function processPainting(painting: string): Promise { + const paintingBuffer = Buffer.from(painting, 'base64'); + let output: Uint8Array; + try { + output = inflate(paintingBuffer); + } catch (err) { + logger.error(err, 'Could not decompress painting'); + return null; + } + let tga: TGA; + try { + tga = new TGA(Buffer.from(output.buffer)); + } catch (e) { + logger.error(e, 'Could not parse painting'); + return null; + } + const png = new PNG({ + width: tga.width, + height: tga.height + }); + png.data = tga.pixels; + return PNG.sync.write(png); +} + +/** + * Rezise an image. + * @param data base64-encoded input image, most codecs OK (check Sharp docs) + * @param width Target width in pixels + * @param height Target height in pixels + * @returns New image, same codec as the input (not base64'd!) + */ +export async function resizeImage(data: string, width: number, height: number): Promise { + const image = Buffer.from(data, 'base64'); + return sharp(image) + .resize({ height, width }) + .toBuffer() + .catch((err) => { + logger.error(err, 'Could not resize image!'); + throw err; + }); +} + +export async function getTGAFromPNG(image: Buffer): Promise { + const pngData = await imagePixels(Buffer.from(image)); + const tga = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); + let output: Uint8Array; + try { + output = deflate(tga, { level: 6 }); + } catch (err) { + logger.error(err, 'Could not decompress image'); + return null; + } + return Buffer.from(output.buffer).toString('base64').trim(); +} + +/** + * Makes a new TGA from BGRX pixel data + * @param width Width of the data + * @param height Height of the data + * @param pixels [B1, G1, R1, X1, B2, R2, G2, X2...] + * @param dontFlipY Invert the y-axis + * @returns Buffer with TGA data + * + * Modified from https://github.com/steel1990/tga/blob/dcd2bff6536c1c75719ed4309389cd66f991d8d3/src/index.js#L16-L45 + */ +function createTgaFromPixelData(width: number, height: number, pixels: Buffer, dontFlipY: boolean): Buffer { + const buffer = Buffer.alloc(18 + pixels.length); + // write header + buffer.writeInt8(0, 0); + buffer.writeInt8(0, 1); + buffer.writeInt8(2, 2); + buffer.writeInt16LE(0, 3); + buffer.writeInt16LE(0, 5); + buffer.writeInt8(0, 7); + buffer.writeInt16LE(0, 8); + buffer.writeInt16LE(0, 10); + buffer.writeInt16LE(width, 12); + buffer.writeInt16LE(height, 14); + buffer.writeInt8(32, 16); + buffer.writeInt8(8, 17); + + let offset = 18; + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4; + buffer.writeUInt8(pixels[idx + 1], offset++); // b + buffer.writeUInt8(pixels[idx + 2], offset++); // g + buffer.writeUInt8(pixels[idx + 3], offset++); // r + buffer.writeUInt8(255, offset++); // a + } + } + + return buffer; +} diff --git a/apps/juxtaposition-ui/src/loggerHttp.ts b/apps/juxtaposition-ui/src/loggerHttp.ts index fd0ab64e..cf21e05a 100644 --- a/apps/juxtaposition-ui/src/loggerHttp.ts +++ b/apps/juxtaposition-ui/src/loggerHttp.ts @@ -4,8 +4,9 @@ import { config } from '@/config'; import { decodeParamPack } from '@/util'; import type { SerializedRequest, SerializedResponse } from 'pino'; import type { Request } from 'express'; +import type { ParamPack } from '@/types/common/param-pack'; -type SerializedNintendoRequest = SerializedRequest & { param_pack?: Record }; +type SerializedNintendoRequest = SerializedRequest & { param_pack?: ParamPack }; function redactHeaders(headers: Record, allowlist: string[]): Record { if (!config.log.sensitive) { diff --git a/apps/juxtaposition-ui/src/middleware/checkBan.js b/apps/juxtaposition-ui/src/middleware/checkBan.js index 30993899..dae1b2a3 100644 --- a/apps/juxtaposition-ui/src/middleware/checkBan.js +++ b/apps/juxtaposition-ui/src/middleware/checkBan.js @@ -38,7 +38,7 @@ async function checkBan(request, response, next) { if (!accessAllowed) { response.status(500); if (request.directory === 'web') { - return response.render('web/login.ejs', { toast: 'No access. Must be tester or dev' }); + return response.render('web/login.ejs', { toast: 'No access. Must be tester or dev', redirect: request.originalUrl }); } else { return response.render('portal/partials/ban_notification.ejs', { user: null, @@ -66,7 +66,7 @@ async function checkBan(request, response, next) { default: banMessage = `${request.user.username} has been banned. \n\nIf you have any questions contact the moderators in the Discord server or forum.`; } - return response.render('web/login.ejs', { toast: banMessage }); + return response.render('web/login.ejs', { toast: banMessage, redirect: request.originalUrl }); } else { return response.render(request.directory + '/partials/ban_notification.ejs', { user: userSettings, diff --git a/apps/juxtaposition-ui/src/middleware/consoleAuth.js b/apps/juxtaposition-ui/src/middleware/consoleAuth.js index 36f815e1..cf685ad1 100644 --- a/apps/juxtaposition-ui/src/middleware/consoleAuth.js +++ b/apps/juxtaposition-ui/src/middleware/consoleAuth.js @@ -7,12 +7,15 @@ async function auth(request, response, next) { if (request.session && request.session.user && request.session.pid && !request.isWrite) { request.user = request.session.user; request.pid = request.session.pid; + request.tokens = request.session.tokens; } else { - request.pid = request.headers['x-nintendo-servicetoken'] ? await util.processServiceToken(request.headers['x-nintendo-servicetoken']) : null; - request.user = request.pid ? await util.getUserDataFromPid(request.pid) : null; + request.tokens = { serviceToken: request.headers['x-nintendo-servicetoken'] }; + request.pid = request.headers['x-nintendo-servicetoken'] ? await util.getPIDFromServiceToken(request.headers['x-nintendo-servicetoken']) : null; + request.user = request.pid ? await util.getUserAccountData(request.pid) : null; request.session.user = request.user; request.session.pid = request.pid; + request.session.tokens = request.tokens; } // Set headers diff --git a/apps/juxtaposition-ui/src/middleware/discovery.js b/apps/juxtaposition-ui/src/middleware/discovery.js index b6e939d0..53325f5f 100644 --- a/apps/juxtaposition-ui/src/middleware/discovery.js +++ b/apps/juxtaposition-ui/src/middleware/discovery.js @@ -19,7 +19,7 @@ async function checkDiscovery(request, response, next) { break; } if (request.directory === 'web') { - return response.render('web/login.ejs', { toast: message }); + return response.render('web/login.ejs', { toast: message, redirect: request.originalUrl }); } else { return response.render('portal/partials/ban_notification.ejs', { user: null, diff --git a/apps/juxtaposition-ui/src/middleware/webAuth.js b/apps/juxtaposition-ui/src/middleware/webAuth.js index 518bd65e..788abf47 100644 --- a/apps/juxtaposition-ui/src/middleware/webAuth.js +++ b/apps/juxtaposition-ui/src/middleware/webAuth.js @@ -24,14 +24,14 @@ async function webAuth(request, response, next) { response.clearCookie('token_type', { domain: cookieDomain, path: '/' }); if (request.path === '/login') { response.locals.lang = util.processLanguage(); - request.token = request.cookies.access_token; + request.tokens = {}; request.paramPackData = null; return next(); } } } - request.token = request.cookies.access_token; + request.tokens = { oauthToken: request.cookies.access_token }; // Open access pages if (isStartOfPath(request.path, '/users/') || diff --git a/apps/juxtaposition-ui/src/server.js b/apps/juxtaposition-ui/src/server.js index 97bc6146..e81af008 100644 --- a/apps/juxtaposition-ui/src/server.js +++ b/apps/juxtaposition-ui/src/server.js @@ -3,6 +3,7 @@ const cookieParser = require('cookie-parser'); const session = require('express-session'); const { RedisStore } = require('connect-redis'); const expressMetrics = require('express-prom-bundle'); +require('express-async-errors'); // See package docs const database = require('@/database'); const { logger } = require('@/logger'); const { loggerHttp } = require('@/loggerHttp'); @@ -77,23 +78,36 @@ app.use(juxt_web); logger.info('Creating 404 status handler'); app.use((req, res) => { req.log.warn('Page not found'); + res.status(404); res.render(req.directory + '/error.ejs', { code: 404, - message: 'Page not found' + message: 'Page not found', + id: req.id }); }); // non-404 error handler logger.info('Creating non-404 status handler'); -app.use((error, request, response) => { - const status = error.status || 500; +app.use((error, req, res, next) => { + if (res.headersSent) { + return next(error); + } + + // small hack because token expiry is weird + if (error.status === 401) { + req.session.user = undefined; + req.session.pid = undefined; + return res.redirect(`/login?redirect=${req.originalUrl}`); + } - response.status(status); + const status = error.status || 500; + res.status(status); - response.json({ - app: 'api', - status, - error: error.message + req.log.error(error, 'Request failed!'); + res.render(req.directory + '/error.ejs', { + code: status, + message: 'Error', + id: req.id }); }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.js index 50cee82d..7577f327 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.js @@ -2,6 +2,7 @@ const crypto = require('crypto'); const express = require('express'); const moment = require('moment'); const multer = require('multer'); +const { resizeImage, getTGAFromPNG } = require('@/images'); const database = require('@/database'); const { POST } = require('@/models/post'); const { SETTINGS } = require('@/models/settings'); @@ -78,7 +79,7 @@ router.get('/accounts/:pid', async function (req, res) { if (!res.locals.moderator) { return res.redirect('/titles/show'); } - const pnid = await util.getUserDataFromPid(req.params.pid).catch((e) => { + const pnid = await util.getUserAccountData(req.params.pid).catch((e) => { logger.error(e, `Could not fetch userdata for ${req.params.pid}`); }); const userContent = await database.getUserContent(req.params.pid); @@ -319,9 +320,9 @@ router.post('/communities/new', upload.fields([{ name: 'browserIcon', maxCount: } // browser icon - const icon128 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 128, 128); - const icon64 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 64, 64); - const icon32 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 32, 32); + const icon128 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 128, 128); + const icon64 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 64, 64); + const icon32 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 32, 32); if (!await util.uploadCDNAsset(`icons/${communityID}/128.png`, icon128, 'public-read') || !await util.uploadCDNAsset(`icons/${communityID}/64.png`, icon64, 'public-read') || @@ -330,11 +331,11 @@ router.post('/communities/new', upload.fields([{ name: 'browserIcon', maxCount: } // TGA icon - const tgaIcon = await util.getTGAFromPNG(icon128); + const tgaIcon = await getTGAFromPNG(icon128); // 3DS Header - const CTRHeader = await util.resizeImage(req.files.CTRbrowserHeader[0].buffer.toString('base64'), 400, 220); + const CTRHeader = await resizeImage(req.files.CTRbrowserHeader[0].buffer.toString('base64'), 400, 220); // Wii U Header - const WiiUHeader = await util.resizeImage(req.files.WiiUbrowserHeader[0].buffer.toString('base64'), 1280, 180); + const WiiUHeader = await resizeImage(req.files.WiiUbrowserHeader[0].buffer.toString('base64'), 1280, 180); if (!await util.uploadCDNAsset(`headers/${communityID}/3DS.png`, CTRHeader, 'public-read') || !await util.uploadCDNAsset(`headers/${communityID}/WiiU.png`, WiiUHeader, 'public-read')) { @@ -444,9 +445,9 @@ router.post('/communities/:id', upload.fields([{ name: 'browserIcon', maxCount: // browser icon if (req.files.browserIcon) { - const icon128 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 128, 128); - const icon64 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 64, 64); - const icon32 = await util.resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 32, 32); + const icon128 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 128, 128); + const icon64 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 64, 64); + const icon32 = await resizeImage(req.files.browserIcon[0].buffer.toString('base64'), 32, 32); if (!await util.uploadCDNAsset(`icons/${communityID}/128.png`, icon128, 'public-read') || !await util.uploadCDNAsset(`icons/${communityID}/64.png`, icon64, 'public-read') || @@ -455,11 +456,11 @@ router.post('/communities/:id', upload.fields([{ name: 'browserIcon', maxCount: } // TGA icon - tgaIcon = await util.getTGAFromPNG(icon128); + tgaIcon = await getTGAFromPNG(icon128); } // 3DS Header if (req.files.CTRbrowserHeader) { - const CTRHeader = await util.resizeImage(req.files.CTRbrowserHeader[0].buffer.toString('base64'), 400, 220); + const CTRHeader = await resizeImage(req.files.CTRbrowserHeader[0].buffer.toString('base64'), 400, 220); if (!await util.uploadCDNAsset(`headers/${communityID}/3DS.png`, CTRHeader, 'public-read')) { return res.sendStatus(422); } @@ -467,7 +468,7 @@ router.post('/communities/:id', upload.fields([{ name: 'browserIcon', maxCount: // Wii U Header if (req.files.WiiUbrowserHeader) { - const WiiUHeader = await util.resizeImage(req.files.WiiUbrowserHeader[0].buffer.toString('base64'), 1280, 180); + const WiiUHeader = await resizeImage(req.files.WiiUbrowserHeader[0].buffer.toString('base64'), 1280, 180); if (!await util.uploadCDNAsset(`headers/${communityID}/WiiU.png`, WiiUHeader, 'public-read')) { return res.sendStatus(422); } diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.jsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.jsx index 0619eb2b..e377a933 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.jsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.jsx @@ -3,12 +3,13 @@ import { buildContext } from '@/services/juxt-web/views/context'; import { CtrMessagesView } from '@/services/juxt-web/views/ctr/messages'; import { PortalMessagesView } from '@/services/juxt-web/views/portal/messages'; import { WebMessagesView } from '@/services/juxt-web/views/web/messages'; +import { processPainting } from '@/images'; +import { getUserFriendPIDs, getUserAccountData, getUserHash, uploadCDNAsset, INVALID_POST_BODY_REGEX } from '@/util'; const crypto = require('crypto'); const express = require('express'); const moment = require('moment'); const snowflake = require('node-snowflake').Snowflake; const database = require('@/database'); -const util = require('@/util'); const { POST } = require('@/models/post'); const { CONVERSATION } = require('@/models/conversation'); const { config } = require('@/config'); @@ -26,9 +27,9 @@ router.get('/', async function (req, res) { router.post('/new', async function (req, res) { let conversation = await database.getConversationByID(req.body.community_id); - const user2 = await util.getUserDataFromPid(req.body.message_to_pid); + const user2 = await getUserAccountData(req.body.message_to_pid); const postID = await generatePostUID(21); - const friends = await util.getFriends(user2.pid); + const friends = await getUserFriendPIDs(user2.pid); if (req.body.community_id === 0) { return res.sendStatus(404); } @@ -70,8 +71,8 @@ router.post('/new', async function (req, res) { let screenshot = null; if (req.body._post_type === 'painting' && req.body.painting) { painting = req.body.painting.replace(/\0/g, '').trim(); - paintingURI = await util.processPainting(painting, true); - if (!await util.uploadCDNAsset(`paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read')) { + paintingURI = await processPainting(painting); + if (!await uploadCDNAsset(`paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read')) { res.status(422); return res.render(req.directory + '/error.ejs', { code: 422, @@ -81,7 +82,7 @@ router.post('/new', async function (req, res) { } if (req.body.screenshot) { screenshot = req.body.screenshot.replace(/\0/g, '').trim(); - if (await util.uploadCDNAsset(`screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read')) { + if (await uploadCDNAsset(`screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read')) { res.status(422); return res.render(req.directory + '/error.ejs', { code: 422, @@ -112,7 +113,7 @@ router.post('/new', async function (req, res) { break; } const body = req.body.body; - if (body && util.INVALID_POST_BODY_REGEX.test(body)) { + if (body && INVALID_POST_BODY_REGEX.test(body)) { // TODO - Log this error return res.sendStatus(422); } @@ -163,8 +164,8 @@ router.post('/new', async function (req, res) { }); router.get('/new/:pid', async function (req, res) { - const user2 = await util.getUserDataFromPid(req.params.pid); - const friends = await util.getFriends(user2.pid); + const user2 = await getUserAccountData(req.params.pid); + const friends = await getUserFriendPIDs(user2.pid); if (!req.user || !user2) { return res.sendStatus(422); } @@ -226,7 +227,7 @@ router.get('/:message_id', async function (req, res) { res.redirect('/'); } const messages = await database.getConversationMessages(conversation.id, 200, 0); - const userMap = await util.getUserHash(); + const userMap = await getUserHash(); res.render(req.directory + '/message_thread.ejs', { moment: moment, user2: user2, diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js index cdead472..0accbe74 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js @@ -2,6 +2,7 @@ const express = require('express'); const moment = require('moment'); const database = require('@/database'); const util = require('@/util'); +const { getUserFriendRequestsIncoming } = util; const router = express.Router(); router.get('/my_news', async function (req, res) { @@ -31,7 +32,7 @@ router.get('/my_news', async function (req, res) { }); router.get('/friend_requests', async function (req, res) { - let requests = (await util.getFriendRequests(req.pid)).reverse(); + let requests = (await getUserFriendRequestsIncoming(req.pid)).reverse(); const now = new Date(); requests = requests.filter(request => new Date(Number(request.expires) * 1000) > new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000)); const userMap = util.getUserHash(); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js index 18fc31c3..8a67c99e 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js @@ -11,7 +11,9 @@ const { REPORT } = require('@/models/report'); const upload = multer({ dest: 'uploads/' }); const redis = require('@/redisCache'); const { config } = require('@/config'); +const { processBmpPainting, processPainting } = require('@/images'); const router = express.Router(); +const { getPostById } = require('@/api/post'); const postLimit = rateLimit({ windowMs: 15 * 1000, // 30 seconds @@ -41,7 +43,10 @@ const yeahLimit = rateLimit({ }); router.get('/:post_id/oembed.json', async function (req, res) { - const post = await database.getPostByID(req.params.post_id.toString()); + const post = await getPostById(req.tokens, req.params.post_id); + if (!post) { + return res.sendStatus(404); + } const doc = { author_name: post.screen_name, author_url: 'https://juxt.pretendo.network/users/show?pid=' + post.pid @@ -108,21 +113,22 @@ router.post('/new', postLimit, upload.none(), async function (req, res) { router.get('/:post_id', async function (req, res) { const userSettings = await database.getUserSettings(req.pid); const userContent = await database.getUserContent(req.pid); - let post = await database.getPostByID(req.params.post_id.toString()); - if (post === null) { + + const post = await getPostById(req.tokens, req.params.post_id); + if (!post) { return res.redirect('/404'); } if (post.parent) { - post = await database.getPostByID(post.parent); - if (post === null) { - return res.sendStatus(404); + const parent = await getPostById(req.tokens, post.parent); + if (!parent) { + return res.redirect('/404'); } - return res.redirect(`/posts/${post.id}`); + return res.redirect(`/posts/${parent.id}`); } const community = await database.getCommunityByID(post.community_id); const communityMap = await util.getCommunityHash(); const replies = await database.getPostReplies(req.params.post_id.toString(), 25); - const postPNID = await util.getUserDataFromPid(post.pid); + const postPNID = await util.getUserAccountData(post.pid); res.render(req.directory + '/post.ejs', { moment: moment, userSettings: userSettings, @@ -172,7 +178,7 @@ router.post('/:post_id/new', postLimit, upload.none(), async function (req, res) router.post('/:post_id/report', upload.none(), async function (req, res) { const { reason, message, post_id } = req.body; - const post = await database.getPostByID(post_id); + const post = await getPostById(req.tokens, post_id); if (!reason || !post_id || !post) { return res.redirect('/404'); } @@ -254,11 +260,11 @@ async function newPost(req, res) { let screenshot = null; if (req.body._post_type === 'painting' && req.body.painting) { if (req.body.bmp === 'true') { - painting = await util.processPainting(req.body.painting.replace(/\0/g, '').trim(), false); + painting = await processBmpPainting(req.body.painting.replace(/\0/g, '').trim()); } else { painting = req.body.painting; } - paintingURI = await util.processPainting(painting, true); + paintingURI = await processPainting(painting); if (!await util.uploadCDNAsset(`paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read')) { res.status(422); return res.render(req.directory + '/error.ejs', { diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js index 69d7b609..4ae73ded 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js @@ -44,7 +44,7 @@ router.post('/newUser', async function (req, res) { return res.sendStatus(504); } - await util.create_user(req.pid, req.body.experience, req.body.notifications); + await util.createUser(req.pid, req.body.experience, req.body.notifications); if (await database.getUserSettings(req.pid) !== null) { res.sendStatus(200); } else { diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js index 99c77257..10807e95 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js @@ -3,6 +3,7 @@ const multer = require('multer'); const moment = require('moment'); const database = require('@/database'); const util = require('@/util'); +const { getUserFriendPIDs } = util; const { POST } = require('@/models/post'); const { SETTINGS } = require('@/models/settings'); const redis = require('@/redisCache'); @@ -138,7 +139,7 @@ async function userPage(req, res, userID) { } const pnid = userID === req.pid ? req.user - : await util.getUserDataFromPid(userID).catch((e) => { + : await util.getUserAccountData(userID).catch((e) => { logger.error(e, `Could not fetch userdata for ${req.params.pid}`); }); const userContent = await database.getUserContent(userID); @@ -155,7 +156,7 @@ async function userPage(req, res, userID) { const numPosts = await database.getTotalPostsByUserID(userID); const communityMap = await util.getCommunityHash(); - const friends = await util.getFriends(userID); + const friends = await getUserFriendPIDs(userID); let parentUserContent; if (pnid.pid !== req.pid) { @@ -194,12 +195,12 @@ async function userPage(req, res, userID) { } async function userRelations(req, res, userID) { - const pnid = userID === req.pid ? req.user : await util.getUserDataFromPid(userID); + const pnid = userID === req.pid ? req.user : await util.getUserAccountData(userID); const userContent = await database.getUserContent(userID); const link = (pnid.pid === req.pid) ? '/users/me/' : `/users/${userID}/`; const userSettings = await database.getUserSettings(userID); const numPosts = await database.getTotalPostsByUserID(userID); - const friends = await util.getFriends(userID); + const friends = await getUserFriendPIDs(userID); let parentUserContent; if (pnid.pid !== req.pid) { parentUserContent = await database.getUserContent(req.pid); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js index 5aa45725..a852ad94 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js @@ -8,22 +8,22 @@ const { logger } = require('@/logger'); const cookieDomain = config.http.cookieDomain; router.get('/', async function (req, res) { - res.render(req.directory + '/login.ejs', { toast: null }); + res.render(req.directory + '/login.ejs', { toast: null, redirect: req.query.redirect ?? '/' }); }); router.post('/', async (req, res) => { - const { username, password } = req.body; + const { username, password, redirect } = req.body; const login = await util.login(username, password).catch((e) => { switch (e.details) { case 'INVALID_ARGUMENT: User not found': - res.render(req.directory + '/login.ejs', { toast: 'Username was invalid.' }); + res.render(req.directory + '/login.ejs', { toast: 'Username was invalid.', redirect }); break; case 'INVALID_ARGUMENT: Password is incorrect': - res.render(req.directory + '/login.ejs', { toast: 'Password was incorrect.' }); + res.render(req.directory + '/login.ejs', { toast: 'Password was incorrect.', redirect }); break; default: logger.error(e, `Login error for ${username}`); - res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.' }); + res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.', redirect }); break; } }); @@ -33,7 +33,7 @@ router.post('/', async (req, res) => { const PNID = await util.getUserDataFromToken(login.accessToken); if (!PNID) { - return res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.' }); + return res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.', redirect }); } let discovery = await database.getEndPoint(config.serverEnvironment); @@ -65,7 +65,11 @@ router.post('/', async (req, res) => { res.cookie('access_token', login.accessToken, { domain: cookieDomain, maxAge: expiration }); res.cookie('refresh_token', login.refreshToken, { domain: cookieDomain }); res.cookie('token_type', 'Bearer', { domain: cookieDomain }); - res.redirect('/'); + + /* Only allow relative URLs (leading /) or absolute ones on config.http.baseUrl. */ + const safe_redirect = new URL(redirect, config.http.baseUrl).origin == config.http.baseUrl ? redirect : '/'; + + res.redirect(safe_redirect); }); module.exports = router; diff --git a/apps/juxtaposition-ui/src/translations/ast.json b/apps/juxtaposition-ui/src/translations/ast.json index 0967ef42..a9f0bdba 100644 --- a/apps/juxtaposition-ui/src/translations/ast.json +++ b/apps/juxtaposition-ui/src/translations/ast.json @@ -1 +1,126 @@ -{} +{ + "global": { + "communities": "Comunidades", + "more": "Cargar más publicaciones", + "next": "Próximo", + "yeahs": "Sí", + "messages": "Mensajes", + "activity_feed": "Fuente de actividades", + "exit": "Salida", + "back": "Atrás", + "notifications": "Notificaciones", + "save": "Salvar", + "no_posts": "Sin publicaciones", + "private": "Privado", + "close": "Cerrar", + "user_page": "Página de usuario", + "go_back": "Volver" + }, + "community": { + "posts": "Publicaciones", + "tags": "Etiquetas", + "followers": "Seguidores", + "following": "Siguiente", + "follow": "Seguir a la comunidad", + "recent": "Publicaciones recientes", + "popular": "Publicaciones populares", + "new": "Nueva publicación", + "verified": "Publicaciones verificadas" + }, + "all_communities": { + "show_all": "Mostrar todo", + "announcements": "Anuncios", + "search": "Buscar comunidades...", + "text": "Todas las comunidades", + "ann_string": "¡Haga clic aquí para ver los anuncios recientes!", + "new_communities": "Nuevas comunidades", + "popular_places": "Lugares populares" + }, + "language": "Asturies", + "setup": { + "rules_text": { + "eleventh": "Nuestro objetivo es hacer que Juxt sea divertido y agradable para todos. En el caso de que alguien infrinja el Código de conducta de Juxt, tomaremos las medidas adecuadas, incluido el bloqueo del usuario o la consola infractores.", + "tenth": "Violaciones del Código de Conducta", + "third": "Juxt contiene muchas comunidades de juegos donde personas de todo el mundo pueden compartir sus opiniones. Cuando publiques en una comunidad, recuerda que todos pueden verlo, así que exprésate de una manera que todos puedan disfrutar. Utilice el sentido común y piense antes de publicar. Juxt es un servicio al que también se puede acceder desde Internet, así que ten en cuenta que las personas que no usan Juxt también pueden ver tus publicaciones. Además, cualquier comentario que hagas en las publicaciones de tus amigos será visto no sólo por tus amigos sino también por personas de todo el mundo. Por favor tenga esto en cuenta.", + "thirteenth": "Si estás publicando en la comunidad de un juego que has jugado, tus publicaciones tendrán un icono que indica que lo has jugado.", + "fifth": "Para que Juxt siga siendo un lugar divertido para todos, le pedimos que sea considerado con otros usuarios. Ayúdanos a mantener una experiencia agradable en Juxt al no publicar nada inapropiado u ofensivo.", + "ninth": "Algunas personas acuden a Juxt en busca de consejos y trucos para juegos, pero otras quieren descubrir los secretos de un juego por su cuenta. Las publicaciones que revelan secretos de un juego o su historia se denominan \"spoilers\". Si estás publicando algo sobre un juego que podría ser un spoiler, asegúrate de marcar la casilla Spoilers antes de enviar tu publicación. De esta manera, las personas que no quieren ser mimadas no verán tu publicación.", + "fourth": "Sean amables los unos con los otros", + "second": "Las publicaciones se pueden ver en todo el mundo", + "sixth": "No publique información personal, suya o de otros", + "first": "Las siguientes son algunas pautas importantes para hacer de Juxt una experiencia divertida y agradable para todos. El Código de Conducta de Juxt contiene información detallada, así que léalo detenidamente.", + "eighth": "No publiques spoilers", + "seventh": "Recuerda, conocer a alguien en Juxt no es lo mismo que conocerlo en la vida real. Nunca comparta su dirección de correo electrónico, dirección particular, nombre del trabajo o la escuela, u otra información de identificación personal con nadie en Juxt, y nunca comparta la información de nadie más. Además, si alguien que conoces en Juxt te invita a conocerlo en el mundo real, no aceptes. Juxt es una comunidad en línea y no debe usarse para organizar reuniones en el mundo real.", + "twelfth": "¿Has jugado a ese juego?" + }, + "beta_text": { + "third": "El sitio web, su software y todo el contenido que se encuentra en él se proporcionan \"tal cual\" y \"según disponibilidad\". Pretendo Network no ofrece ninguna garantía, ya sea expresa o implícita, en cuanto a la idoneidad o usabilidad del sitio web, su software o cualquiera de sus contenidos.", + "second": "Esto puede incluir un borrado total de la base de datos al final o durante el período beta.", + "first": "Estás a punto de probar la primera beta pública de Juxt. Esto significa que todavía hay muchas cosas en el aire, y muchas cosas pueden cambiar en cualquier momento." + }, + "experience_text": { + "expert": "Experto", + "info": "Cuéntanos cómo describirías tu nivel de experiencia con los juegos. Puede cambiar esta configuración más adelante.", + "intermediate": "Intermedio", + "beginner": "Principiante" + }, + "guest_button": "Entiendo", + "done_button": "¡Vamos!", + "guest": "Modo invitado", + "ready": "Listo para empezar a usar Juxt", + "guest_text": "No podemos verificar tu cuenta en este momento, lo que probablemente significa que estás iniciando sesión con una cuenta de Nintendo Network. Podrás seguir navegando por Juxt, pero no podrás hacerlo ¡sí! publica o haz tus propias publicaciones hasta que inicies sesión con una cuenta de Pretendo Network. Para obtener más información, consulte el servidor de Discord.", + "experience": "Experiencia de juego", + "google": "Google Analytics", + "rules": "Reglas de Juxt", + "welcome_text": "Juxt es una comunidad de jugadores que conecta a personas de todo el mundo que usan personajes Mii. Usa Juxt para compartir tus experiencias de juego y conocer gente de todo el mundo.", + "google_text": "Juxtaposition utiliza Google Analytics para realizar un seguimiento de cómo los usuarios utilizan nuestro servicio. Los datos recopilados incluyen, entre otros, el tiempo de visita, las páginas visitadas y el tiempo de permanencia en el sitio. Estos datos no se adjuntan a usted de ninguna manera y no se utilizan para publicidad por nuestra parte o por parte de Google.", + "info": "¿Qué es la yuxtaposición?", + "info_text": "Juxt es una comunidad de juegos impulsada por el usuario en la que puedes interactuar con la gente\n en todo el mundo. Puedes escribir o dibujar publicaciones en las comunidades del juego o enviar mensajes directamente a tus amigos.", + "ready_text": "Primero, echa un vistazo a algunas comunidades y mira sobre qué publican personas de todo el mundo. Aproveche esta oportunidad para familiarizarse con Juxt. ¡Es posible que hagas nuevos descubrimientos en el camino!", + "done": "¡Diviértete en Juxt!", + "beta": "Descargo de responsabilidad de la beta", + "welcome": "¡Bienvenido a la yuxtaposición!" + }, + "notifications": { + "none": "No hay notificaciones.", + "new_follower": "¡Te seguí!" + }, + "user_page": { + "follow_user": "Seguir", + "game_experience": "Experiencia de juego", + "no_following": "No seguir a nadie", + "befriend": "Ofrecer amistad", + "posts": "publicación", + "friends": "Amigos", + "following_user": "Siguiente", + "pending": "Pendiente", + "followers": "Seguidores", + "unfriend": "Dejar de ser amigo", + "no_followers": "Sin seguidores", + "no_friends": "Sin amigos", + "birthday": "Cumpleaños", + "country": "País", + "following": "Siguiente" + }, + "new_post": { + "swearing": "La publicación no puede contener lenguaje explícito.", + "text_hint": "Pulsa aquí para hacer una publicación.", + "new_post_text": "Nueva publicación", + "post_to": "Publicar en" + }, + "user_settings": { + "show_game": "Mostrar experiencia de juego en el perfil", + "show_comment": "Mostrar comentario en el perfil", + "show_birthday": "Mostrar cumpleaños en el perfil", + "profile_settings": "Configuración perfil", + "profile_comment": "Comentario de perfil", + "swearing": "El comentario de perfil no puede contener lenguaje explícito.", + "show_country": "Mostrar país en el perfil" + }, + "activity_feed": { + "empty": "Está vacío aquí. ¡Intenta seguir a alguien!" + }, + "messages": { + "coming_soon": "Mensajes aún no está listo. ¡Vuelve pronto!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/be.json b/apps/juxtaposition-ui/src/translations/be.json index 8d174e04..4af60478 100644 --- a/apps/juxtaposition-ui/src/translations/be.json +++ b/apps/juxtaposition-ui/src/translations/be.json @@ -43,7 +43,84 @@ "posts": "Пасты", "friends": "Сябры", "following": "Падпіскі", - "followers": "Падпісчыкі" + "followers": "Падпісчыкі", + "follow_user": "Сачыць", + "following_user": "Далей", + "befriend": "Сябраваць", + "pending": "Дапытлівы", + "no_following": "", + "unfriend": "", + "no_followers": "", + "no_friends": "" + }, + "language": "Беларуская", + "setup": { + "experience_text": { + "beginner": "Пачатковец", + "expert": "Эксперт", + "info": "", + "intermediate": "" + }, + "rules_text": { + "tenth": "Парушэнні Кодэкса паводзін", + "thirteenth": "Калі вы размяшчаеце паведамленні ў суполцы аб гульні, у якую вы гулялі, вашы паведамленні будуць мець значок, які паказвае, што вы ў яе гулялі.", + "eleventh": "", + "third": "", + "fifth": "", + "ninth": "", + "fourth": "", + "second": "", + "sixth": "", + "first": "", + "eighth": "", + "seventh": "", + "twelfth": "" + }, + "experience": "Вопыт гульні", + "beta_text": { + "third": "", + "second": "", + "first": "" + }, + "guest_button": "", + "done_button": "", + "guest": "", + "ready": "", + "guest_text": "", + "google": "", + "rules": "", + "welcome_text": "", + "google_text": "", + "info": "", + "info_text": "", + "ready_text": "", + "done": "", + "beta": "", + "welcome": "" + }, + "notifications": { + "none": "", + "new_follower": "" + }, + "new_post": { + "swearing": "", + "text_hint": "", + "new_post_text": "", + "post_to": "" + }, + "user_settings": { + "show_game": "", + "show_comment": "", + "show_birthday": "", + "profile_settings": "", + "profile_comment": "", + "swearing": "", + "show_country": "" + }, + "activity_feed": { + "empty": "" }, - "language": "Беларуская" + "messages": { + "coming_soon": "" + } } diff --git a/apps/juxtaposition-ui/src/translations/ca.json b/apps/juxtaposition-ui/src/translations/ca.json index 0967ef42..12c23740 100644 --- a/apps/juxtaposition-ui/src/translations/ca.json +++ b/apps/juxtaposition-ui/src/translations/ca.json @@ -1 +1,126 @@ -{} +{ + "global": { + "communities": "Comunitats", + "more": "Carregar més publicacions", + "next": "Següent", + "messages": "Missatges", + "activity_feed": "Taxes d'activitat", + "exit": "Sortir", + "back": "Enrere", + "notifications": "Notificacions", + "save": "Desa", + "no_posts": "No hi ha publicacions", + "private": "Privat", + "close": "Tancar", + "user_page": "Pàgina d'usuari", + "go_back": "Torna", + "yeahs": "Mola" + }, + "notifications": { + "none": "No hi ha notificacions.", + "new_follower": "t'ha seguit!" + }, + "user_page": { + "follow_user": "Seguir", + "game_experience": "Experiència de joc", + "no_following": "No segueix a ningú", + "befriend": "Fes-te amic", + "posts": "Publicacions", + "friends": "Amics", + "following_user": "Seguint", + "pending": "Pendent", + "followers": "Seguidors", + "no_followers": "No hi ha seguidors", + "no_friends": "Sense amics", + "birthday": "Aniversari", + "country": "País", + "following": "Seguint", + "unfriend": "Eliminar amic" + }, + "community": { + "new": "Publicació nova", + "posts": "Publicacions", + "tags": "Etiquetes", + "followers": "Seguidors", + "following": "Seguint", + "follow": "Segueix aquesta comunitat", + "verified": "Publicacions verificades", + "recent": "Publicacions recents", + "popular": "Publicacions populars" + }, + "user_settings": { + "show_game": "Mostra la experiència de joc al perfil", + "show_comment": "Mostra el teu comentari al perfil", + "show_birthday": "Mostra el teu aniversari al perfil", + "profile_settings": "Configuració del perfil", + "profile_comment": "Comentari del perfil", + "swearing": "El comentari del perfil no pot contenir llenguatge explícit o inapropiat.", + "show_country": "Mostra el teu país al perfil" + }, + "new_post": { + "text_hint": "Toca aquí per fer una publicació.", + "new_post_text": "Publicació nova", + "post_to": "Publica a", + "swearing": "La teva publicació no pot contenir llenguatge explícit." + }, + "all_communities": { + "show_all": "Mostra-ho tot", + "search": "Cerca comunitats...", + "text": "Totes les comunitats", + "ann_string": "Toca aquí per veure els anuncis recents!", + "new_communities": "Noves Comunitats", + "popular_places": "Comunitats Populars", + "announcements": "Anuncis" + }, + "language": "Català", + "activity_feed": { + "empty": "Aquí està buit. Pots provar de seguir algú!" + }, + "setup": { + "welcome": "Benvingut a Juxtaposition!", + "rules_text": { + "eleventh": "El nostre objectiu és mantenir Juxt divertit i agradable per a tothom. En el cas que algú infringeixi el Codi de Conducta de Juxt, prendrem les mesures adequades, fins i tot bloquejant l'usuari o la consola infractors.", + "tenth": "Infraccions del codi de conducta", + "third": "Juxt conté moltes comunitats de jocs on persones de tot el món poden compartir els seus pensaments. Quan publiqueu en una comunitat, recordeu que tothom ho pot veure, així que si us plau, expresseu-vos d'una manera que tothom pugui gaudir. Fes servir el sentit comú i pensa abans de publicar. Juxt és un servei que també és accessible des d'Internet, així que tingueu en compte que les persones que no utilitzin Juxt també poden veure les vostres publicacions. A més, els comentaris que feu a les publicacions dels vostres amics no només els veuran els vostres amics, sinó també gent d'arreu del món. Si us plau, tingueu això en compte.", + "thirteenth": "Si publiques a la comunitat un joc al qual has jugat, a les teves publicacions hi haurà una icona que indica que l'has jugat.", + "fifth": "Per tal de mantenir Juxt un lloc divertit per a tothom, us demanem que sigueu considerats amb els altres usuaris. Ajudeu-nos a mantenir Juxt una experiència agradable no publicant res inadequat o ofensiu.", + "ninth": "Algunes persones vénen a Juxt buscant consells i trucs per als jocs, però altres volen descobrir els secrets d'un joc pel seu compte. Les publicacions que revelen secrets d'un joc o la seva història s'anomenen \"spoilers\". Si publiqueu alguna cosa sobre un joc que podria ser un spoiler, assegureu-vos de marcar la casella Spoilers abans d'enviar la vostra publicació. D'aquesta manera, les persones que no vulguin ser espatllades no veuran la vostra publicació.", + "fourth": "Sigueu amables els uns amb els altres", + "second": "Les publicacions es poden veure a tot el món", + "sixth": "No publiquis informació personal, teva o d'altres persones", + "first": "A continuació es detallen algunes pautes importants per fer de Juxt una experiència divertida i agradable per a tothom. El Codi de Conducta de Juxt conté informació detallada, així que llegiu-la atentament.", + "eighth": "No publiqueu revelacions", + "seventh": "Recordeu, no és el mateix conèixer algú a Juxt que conèixer-lo a la vida real. No compartiu mai la vostra adreça de correu electrònic, adreça de casa, nom de la feina o de l'escola, ni cap altra informació d'identificació personal amb ningú a Juxt, i mai compartiu la informació de ningú més. A més, si algú que coneixes a Juxt et convida a conèixer-lo al món real, no acceptis. Juxt és una comunitat en línia i no s'ha d'utilitzar per organitzar trobades del món real.", + "twelfth": "Has jugat a aquest joc?" + }, + "beta_text": { + "third": "El lloc web, el seu programari i tot el contingut que s'hi troba es proporcionen \"tal qual\" i \"segons estigui disponible\". La Xarxa de Pretendo no dóna cap garantia, ja sigui expressa o implícita, sobre la idoneïtat o usabilitat del lloc web, el seu programari o qualsevol dels seus continguts.", + "second": "Això pot incloure una eliminació total de la base de dades al final o durant el període beta.", + "first": "Estàs a punt de provar la primera beta pública de Juxt. Això vol dir que encara hi ha moltes coses en l'aire, i moltes coses poden canviar en qualsevol moment." + }, + "experience_text": { + "expert": "Expert", + "info": "Si us plau, explica'ns com descriuries el teu nivell d'experiència amb els jocs. Podeu canviar aquesta configuració més endavant.", + "intermediate": "Intermedi", + "beginner": "Principiant" + }, + "guest_button": "Comprenc", + "done_button": "Som-hi!", + "guest": "Mode convidat", + "ready": "A punt per començar a utilitzar Juxt", + "guest_text": "No podem verificar el vostre compte en aquest moment, cosa que probablement significa que actualment esteu iniciant la sessió amb un compte de Nintendo Network. Encara podreu navegar per Juxt, però no podreu fer-ho. publica o fes publicacions pròpies fins que iniciïs sessió amb un compte de Pretendo Network. Per obtenir més informació, consulteu el servidor de Discord.", + "experience": "Experiència de joc", + "google": "Google Analytics", + "rules": "Regles de Juxt", + "welcome_text": "Juxt és una comunitat de jocs que connecta persones de tot el món mitjançant personatges Mii. Utilitzeu Juxt per compartir les vostres experiències de joc i conèixer gent de tot el món.", + "google_text": "La juxtaposició utilitza Google Analytics per tal de fer un seguiment de com els usuaris utilitzen el nostre servei. Les dades recopilades inclouen, entre d'altres, el temps de visita, les pàgines visitades i el temps dedicat al lloc. Aquestes dades no se li adjunten de cap manera, i no s'utilitzen per a anuncis per nosaltres o Google.", + "info": "Què és Juxtaposition?", + "info_text": "Juxt és una comunitat de jocs impulsada per l'usuari on es pot interactuar amb la gent\n a tot el món. Pots escriure o dibuixar publicacions en comunitats de jocs o enviar missatges directament als teus amics.", + "ready_text": "En primer lloc, fes un cop d'ull a algunes comunitats i descobreix què publiquen persones de tot el món. Aprofita aquesta oportunitat per familiaritzar-te amb Juxt. Podeu fer alguns descobriments nous al llarg del camí!", + "done": "Diverteix-te a Juxt!", + "beta": "Exempció de responsabilitat sobre la beta" + }, + "messages": { + "coming_soon": "L'opció Missatges encara no està preparada. Torna-ho a comprovar aviat!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/cs.json b/apps/juxtaposition-ui/src/translations/cs.json index 64d44c35..40e01419 100644 --- a/apps/juxtaposition-ui/src/translations/cs.json +++ b/apps/juxtaposition-ui/src/translations/cs.json @@ -70,7 +70,7 @@ "new_post": { "new_post_text": "Nový příspěvek", "swearing": "Příspěvky nesmí obsahovat nevhodné výrazy.", - "text_hint": "Klikněte zde pro vytvoření příspěvku.", + "text_hint": "Klikněte sem pro vytvoření příspěvku.", "post_to": "Přispět do" }, "setup": { @@ -95,9 +95,9 @@ "first": "Chystáte se vyzkoušet první veřejnou betu Juxtu. To znamená, že mnohé je stále ve hvězdách a mnohé se může kdykoli změnit." }, "experience_text": { - "expert": "Expert", + "expert": "Pokročilý", "info": "Sdělte nám, prosím, jak byste popsal(a) Vaši zkušenost s hraním. Toto nastavení můžete změnit později.", - "intermediate": "Pokročilý", + "intermediate": "Středně pokročilý", "beginner": "Začátečník" }, "guest_button": "Rozumím", diff --git a/apps/juxtaposition-ui/src/translations/cy.json b/apps/juxtaposition-ui/src/translations/cy.json new file mode 100644 index 00000000..030b8dd8 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/cy.json @@ -0,0 +1,126 @@ +{ + "user_page": { + "country": "Gwlad", + "game_experience": "Profiad gem", + "followers": "Dilynwyr", + "following": "Dilynwch", + "pending": "Yn yr arfaeth", + "unfriend": "Ddim bod yn ffrind", + "no_friends": "Dim Ffrindiau", + "no_following": "Ddim yn dilynwch unrhyw", + "posts": "Swyddi", + "friends": "Ffrindiau", + "follow_user": "Dilyn", + "following_user": "Yn dilyn", + "befriend": "Bod yn ffrind", + "no_followers": "Ddim dilynwyr", + "birthday": "Penblwydd" + }, + "global": { + "back": "Yn ôl", + "yeahs": "Ydychau", + "more": "Swyddi mwy llwyth", + "no_posts": "Dim llwythau", + "private": "Preifat", + "close": "Cau", + "save": "Safiwch", + "exit": "Adael", + "next": "Nesaf", + "user_page": "Tudalen Defnyddiwr", + "activity_feed": "Porthiant gweithgaredd", + "communities": "Cymunedau", + "messages": "Negeuseon", + "notifications": "Hysbysiadau", + "go_back": "Fynd Yn ôl" + }, + "language": "Cymraeg", + "all_communities": { + "announcements": "Cyhoeddiad", + "ann_string": "Clicwch yma i spio cyhoeddiad newydd!", + "show_all": "Dangos Pob beth", + "search": "Chwilio Cymunedau...", + "text": "Pob cymunedau", + "popular_places": "Lleoedd Poblogaidd", + "new_communities": "Gymunedau Newydd" + }, + "community": { + "followers": "Dilynwyr", + "follow": "Dilyn Cymuned", + "popular": "Llwythyr poblogaidd", + "verified": "Swyddi gwirio", + "new": "Swydd newydd", + "following": "Dilynwch", + "posts": "Llywythau", + "tags": "Tagiau", + "recent": "Llywthau diweddar" + }, + "user_settings": { + "show_comment": "Ddangos sylwadau ar profil", + "profile_settings": "Gosodiadau profil", + "show_country": "Ddangos Gwlad Ar Profil", + "show_birthday": "Ddangos Penblwydd Ar Profil", + "profile_comment": "Sylwadau profil", + "swearing": "Ni all gynnwys iaith eglur ar sylwadau profil.", + "show_game": "" + }, + "new_post": { + "new_post_text": "Llwyth newydd", + "post_to": "Llwyth I", + "text_hint": "Tapiwch yma i creu llwyth.", + "swearing": "Llwyth ddim gallu cynnwys iaith eglur." + }, + "setup": { + "rules_text": { + "second": "Pobol dros y byd yn gallu spio ar eich llwythau", + "eighth": "Ddim Llwythio Sbeilwyr", + "twelfth": "Ydach chi wedi chwarae gem yna?", + "eleventh": "", + "tenth": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "fourth": "", + "sixth": "", + "first": "", + "seventh": "" + }, + "ready": "Barod I i ddechrau defnyddio juxt", + "done": "Cael Hwyl Yn Juxt!", + "done_button": "Fynd!", + "guest": "Modd gwestai", + "guest_button": "Dwi'n Deall", + "welcome": "Croeso I Jusxtaposition!", + "rules": "Rheol Juxt", + "info": "Beth ydy juxtaposition?", + "beta_text": { + "third": "", + "second": "", + "first": "" + }, + "experience_text": { + "expert": "", + "info": "", + "intermediate": "", + "beginner": "" + }, + "guest_text": "", + "experience": "", + "google": "", + "welcome_text": "", + "google_text": "", + "info_text": "", + "ready_text": "", + "beta": "" + }, + "activity_feed": { + "empty": "Maen wag yma. Ceishwch dilyn rhywun!" + }, + "notifications": { + "none": "Ddim hysbysiadau.", + "new_follower": "Wedi dilynwch chdi!" + }, + "messages": { + "coming_soon": "Nif yw negeseuon yn barod. eto gwiriwch yn ol yn fuan!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/da.json b/apps/juxtaposition-ui/src/translations/da.json new file mode 100644 index 00000000..edc32974 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/da.json @@ -0,0 +1,126 @@ +{ + "new_post": { + "swearing": "Opslag må ikke indeholde upassende sprog.", + "new_post_text": "Ny Post", + "post_to": "Post til", + "text_hint": "Tryk her for at lave et opslag." + }, + "setup": { + "welcome": "Velkommen til Juxtaposition!", + "welcome_text": "Juxt er et spilfællesskab, der forbinder folk over hele verdenen ved brug af Mii karakterer. Brug Juxt til at dele dine spiloplevelser og mød nye folk over hele verdenen rundt.", + "rules": "Juxt Regler", + "info": "Hvad er Juxtaposition?", + "beta_text": { + "first": "Du er i gang med at prøve den første offentlige beta af Juxt. Dette betyder at der kan foretages en masse ændringer hen af vejen.", + "third": "Hjemmesiden, dets software og alt indhold der findes på siden er stillet til rådighed på en \"som der er\" og \"som tilgængeligt\" grundlag. Pretendo Netværket udsteder ikke nogen garantier, eten direkte udtrykt eller underforstået, for at egnetheden eller brugbarheden af hjemmesiden, dets software eller noget at dets indhold.", + "second": "Dette kan inkludere en fuldstændig fjernelse af alt data i løbet af eller i slutningen betaen." + }, + "rules_text": { + "first": "Det følgende er nogle vigtige retningslinjer, der sørger for at Juxt er en god oplevelse for alle. Juxts adfærdskodeks indeholder vigtig information, så vær rar med at give dig selv tid til at læse det omhyggeligt.", + "sixth": "Del ikke personlige oplysninger - Dine eller Andres", + "twelfth": "Har du spillet det spil?", + "fourth": "Vær sød or rare ved hinanden", + "second": "Oplæg Kan Ses Verdenen Over", + "eighth": "Del ikke Spoilere", + "tenth": "Retningslinjer Overtrædelser", + "eleventh": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "seventh": "" + }, + "beta": "Beta Ansvarsfralæggelse", + "done_button": "Lad os komme i gang!", + "guest_button": "Jeg forstår", + "experience": "Spilerfaring", + "experience_text": { + "info": "Fortæl os hvordan du ville forklare dit niveau af erfaring med spil. Du kan ændre denne indstilling senere.", + "beginner": "Begynder", + "intermediate": "Øvet", + "expert": "Ekspert" + }, + "ready_text": "Først og fremmest check ud nogle af fællesskaberne for at se hvad folk rund om verdenen snakker om. Brug det øjeblik til at gøre dig mere bekendt med Juxt. Måske opstår der endda nogle nye opdagelser på vejen!", + "done": "Mor dig i Juxt!", + "info_text": "Juxt er et brugerdrevet spilfællesskab, hvor du kan kommunikere med folk\nfra hele verdenen. Du kan skrive eller tegne oplag i spilfælleskaber eller sende beskeder direkte til dine venner.", + "guest": "", + "ready": "", + "guest_text": "", + "google": "", + "google_text": "" + }, + "user_page": { + "followers": "Følgere", + "country": "Land", + "birthday": "Fødselsdag", + "friends": "Venner", + "pending": "Afventer", + "no_friends": "Ingen Venner", + "no_followers": "Ingen Følgere", + "game_experience": "Spilerfaring", + "posts": "Oplæg", + "following": "Følger", + "follow_user": "Følg", + "following_user": "Følger", + "befriend": "Bliv Venner", + "unfriend": "Afslut Venskab", + "no_following": "Følger Ikke Nogen" + }, + "user_settings": { + "swearing": "Kommentaren må ikke indeholde upassende sprog.", + "profile_settings": "Profilindstillinger", + "show_birthday": "Vis Fødselsdag på Profil", + "show_country": "Vis Land på Profil", + "show_game": "Vis Spilerfaring på Profil", + "show_comment": "Vis Kommentar på Profil", + "profile_comment": "Profilkommentar" + }, + "global": { + "no_posts": "Ingen Opslag", + "communities": "Fællesskaber", + "notifications": "Notifikationer", + "go_back": "Gå Tilbage", + "yeahs": "Yeah'er", + "more": "Indlæs Flere Opslag", + "exit": "Afslut", + "next": "Næste", + "user_page": "Bruger Side", + "activity_feed": "Aktivitetsside", + "messages": "Beskeder", + "back": "Tilbage", + "private": "Privat", + "close": "Luk", + "save": "Gem" + }, + "all_communities": { + "announcements": "Meddelelser", + "popular_places": "Populære Steder", + "new_communities": "Nye Fællesskaber", + "search": "Søg efter fællesskaber...", + "text": "Alle Fællesskaber", + "ann_string": "Klik her for at se nye meddelelser!", + "show_all": "Vis Alle" + }, + "community": { + "following": "Følger", + "followers": "Følgere", + "tags": "Etiketter", + "verified": "Verificerede Opslag", + "popular": "Populære Opslag", + "new": "Nyt Opslag", + "follow": "Følg Fællesskab", + "posts": "Opslag", + "recent": "Nylige Opslag" + }, + "activity_feed": { + "empty": "Det ser tomt ud her. Prøv at følge nogen!" + }, + "notifications": { + "none": "Ingen notifikationer.", + "new_follower": "følger dig!" + }, + "messages": { + "coming_soon": "Beskeder er ikke klar endnu. Prøv igen senere!" + }, + "language": "Dansk" +} diff --git a/apps/juxtaposition-ui/src/translations/de.json b/apps/juxtaposition-ui/src/translations/de.json index 863b1b8a..a00aa31b 100644 --- a/apps/juxtaposition-ui/src/translations/de.json +++ b/apps/juxtaposition-ui/src/translations/de.json @@ -45,7 +45,7 @@ "friends": "Freunde", "following": "Folge ich", "followers": "Follower", - "follow_user": "Gefolgt von", + "follow_user": "Folgen", "following_user": "Folgt", "befriend": "Befreunden", "pending": "Ausstehend", diff --git a/apps/juxtaposition-ui/src/translations/el.json b/apps/juxtaposition-ui/src/translations/el.json index addfdfa8..52e29099 100644 --- a/apps/juxtaposition-ui/src/translations/el.json +++ b/apps/juxtaposition-ui/src/translations/el.json @@ -1,13 +1,13 @@ { "global": { "back": "Πίσω", - "communities": "κοινότητες", + "communities": "Κοινότητες", "exit": "Έξοδος", - "activity_feed": "Ροή δραστηριότητας", + "activity_feed": "Δραστηριότητες", "messages": "Μηνύματα", "notifications": "Ειδοποιήσεις", - "go_back": "Πίσω", - "yeahs": "Ναιs", + "go_back": "Πήγαινε Πίσω", + "yeahs": "Τέλειο", "no_posts": "Δεν υπάρχουν δημοσιεύσεις", "more": "Φόρτωση περισσότερων δημοσιεύσεων", "private": "Ιδιωτικό", @@ -17,7 +17,7 @@ "user_page": "Προφίλ Χρήστη" }, "all_communities": { - "ann_string": "Πατήστε για περισσότερες ανακοινώσεις!", + "ann_string": "Πατήστε εδώ για να δείτε πρόσφατες ανακοινώσεις!", "text": "Όλες οι κοινότητες", "announcements": "Ανακοινώσεις", "popular_places": "Δημοφιλή μέρη", @@ -25,26 +25,26 @@ "search": "Αναζήτηση κοινοτήτων...", "show_all": "Προβολή όλων" }, - "language": "Γλώσσα", + "language": "Ελληνικά", "community": { - "follow": "Εγγραφή στην κοινότητα", - "following": "Εγγραφήκατε", - "followers": "Συνδρομητές", + "follow": "Ακολουθήστε την κοινότητα", + "following": "Ακολουθείτε", + "followers": "Ακόλουθοι", "posts": "Δημοσιεύσεις", "tags": "Ετικέτες", "recent": "Πρόσφατες δημοσιεύσεις", - "popular": "Δημοφιλείς Αναρτήσεις", + "popular": "Δημοφιλείς Δημοσιεύσεις", "verified": "Επαληθευμένες δημοσιεύσεις", - "new": "Σύνταξη δημοσίευσης" + "new": "Νέα δημοσίευση" }, "user_page": { "birthday": "Γενέθλια", - "game_experience": "Εμπειρία παιχνιδιού", + "game_experience": "Εμπειρία παιχνιδιών", "posts": "Δημοσιεύσεις", "friends": "Φίλοι", "following": "Ακολουθεί", "followers": "Ακόλουθοι", - "follow_user": "Εγγραφή", + "follow_user": "Ακολουθήστε", "following_user": "Ακολουθεί", "befriend": "Αποστολή αιτήματος φιλίας", "pending": "Εκκρεμεί", @@ -56,19 +56,19 @@ }, "user_settings": { "profile_settings": "Ρυθμίσεις προφίλ", - "show_country": "Προβολή χώρας στο προσωπικό προφίλ", - "show_birthday": "Προβολή Γενεθλίων στο προσωπικό προφίλ", + "show_country": "Προβολή χώρας στο προφίλ", + "show_birthday": "Προβολή Γενεθλίων στο προφίλ", "show_game": "Προβολή Εμπειρίας παιχνιδιού στο προφίλ", - "show_comment": "Προβολή σχολίου στο προσωπικό προφίλ", + "show_comment": "Προβολή σχολίου στο προφίλ", "profile_comment": "Σχόλιο προφίλ", - "swearing": "Το σχόλιο του προφίλ δεν μπορεί να περιέχει ακατάλληλη γλώσσα." + "swearing": "Το σχόλιο του προφίλ σας δεν μπορεί να περιέχει ακατάλληλη γλώσσα." }, "activity_feed": { - "empty": "Το μέρος είναι άδειο. Δοκιμάστε να ακολουθήσετε κάποιον χρήστη!" + "empty": "Δεν υπάρχει κανένας εδώ. Δοκιμάστε να ακολουθήσετε κάποιον χρήστη!" }, "notifications": { "none": "Δεν υπάρχουν ειδοποιήσεις.", - "new_follower": "Σας ακολουθεί!" + "new_follower": "σας ακολουθεί!" }, "new_post": { "new_post_text": "Σύνταξη νέας δημοσίευσης", @@ -80,33 +80,33 @@ "coming_soon": "Τα μηνύματα δεν είναι διαθέσιμα προς το παρόν. Δοκιμάστε ξανά σύντομα!" }, "setup": { - "beta": "αποποίηση έκδοσης Beta", + "beta": "Αποποίηση ευθυνών έκδοσης Beta", "beta_text": { "first": "Πρόκειται να δοκιμάσετε την πρώτη δημόσια έκδοση beta του Juxt. Αυτό σημαίνει ότι πολλά πράγματα ενδέχεται να αλλάξουν ανά πάσα στιγμή.", "second": "Αυτό μπορεί και μπορεί να περιλαμβάνει μια πλήρη διαγραφή της βάσης δεδομένων προς το τέλος ή κατά τη διάρκεια της έκδοσης beta.", - "third": "Ο ιστότοπος, το λογισμικό του και όλο το περιεχόμενο που βρίσκεται σε αυτόν παρέχονται σε βάση «ως έχει» και «ως διαθέσιμο». Το Pretendo Network δεν παρέχει καμία εγγύηση, ρητή ή σιωπηρή, ως προς την καταλληλότητα ή τη χρηστικότητα του ιστότοπου, του λογισμικού του ή οποιουδήποτε περιεχομένου του." + "third": "Ο ιστότοπος, το λογισμικό του και όλο το περιεχόμενο που βρίσκεται σε αυτόν παρέχονται σε βάση «όπως είναι» και «όπως διατίθενται». Το Pretendo Network δεν παρέχει καμία εγγύηση, ρητή ή σιωπηρή, ως προς την καταλληλότητα ή τη χρηστικότητα του ιστότοπου, του λογισμικού του ή οποιουδήποτε περιεχομένου του." }, "info": "Τι είναι το Juxtaposition;", - "rules": "Κανόνες", + "rules": "Κανόνες Juxt", "rules_text": { "second": "Οι δημοσιεύσεις μπορούν να προβληθούν δημόσια", "fourth": "Να είστε ευγενικοί με όλους", "fifth": "Για να γίνει το Juxt ένα ευχάριστο μέρος για όλους, παρακαλούμε να είστε ευγενικοί με τους άλλους χρήστες. Βοηθήστε μας να διατηρήσουμε το Juxt ένα ευχάριστο μέρος χωρίς προσβλητικές δημοσιεύσεις ή ακατάλληλο περιεχόμενο.", "sixth": "Ποτέ μην δημοσιεύετε προσωπικές πληροφορίες-είτε δικές σας είτε των άλλων", - "eighth": "Μην δημοσιεύετε spoilers", + "eighth": "Μην δημοσιεύετε σπόιλερ", "tenth": "Παραβιάσεις του Κώδικα Δεοντολογίας", - "eleventh": "Στόχος μας είναι να διατηρήσουμε το Juxt διασκεδαστικό και ευχάριστο για όλους. Σε περίπτωση που κάποιος παραβιάσει τον Κώδικα Συμπεριφοράς της Juxt, θα λάβουμε τα κατάλληλα μέτρα, έως και τον αποκλεισμό του παραβάτη χρήστη ή κονσόλας.", + "eleventh": "Στόχος μας είναι να διατηρήσουμε το Juxt διασκεδαστικό και ευχάριστο για όλους. Σε περίπτωση που κάποιος παραβιάσει τον Κώδικα Συμπεριφοράς του Juxt, θα λάβουμε τα κατάλληλα μέτρα, έως και τον αποκλεισμό του παραβάτη χρήστη ή κονσόλας.", "twelfth": "Έχετε παίξει αυτό το παιχνίδι;", "thirteenth": "Εάν δημοσιεύετε στην κοινότητα ένα παιχνίδι που έχετε παίξει, οι αναρτήσεις σας θα έχουν ένα εικονίδιο που θα υποδεικνύει ότι το έχετε παίξει.", - "first": "Ακολουθούν ορισμένες σημαντικές οδηγίες για να κάνετε το Juxt μια διασκεδαστική και ευχάριστη εμπειρία για όλους. Ο Κώδικας Δεοντολογίας Juxt περιέχει λεπτομερείς πληροφορίες, γι' αυτό παρακαλούμε διαβάστε τον προσεκτικά.", + "first": "Ακολουθούν ορισμένες σημαντικές οδηγίες για να κάνετε το Juxt μια διασκεδαστική και ευχάριστη εμπειρία για όλους. Ο Κώδικας Δεοντολογίας του Juxt περιέχει λεπτομερείς πληροφορίες, γι' αυτό παρακαλούμε διαβάστε τον προσεκτικά.", "third": "Το Juxt περιέχει πολλές κοινότητες παιχνιδιών όπου άνθρωποι από όλο τον κόσμο μπορούν να μοιραστούν τις σκέψεις τους. Όταν δημοσιεύετε σε μια κοινότητα, να θυμάστε ότι μπορούν να το δουν όλοι, γι' αυτό εκφραστείτε με τρόπο που να μπορούν να απολαύσουν όλοι. Χρησιμοποιήστε την κοινή λογική και σκεφτείτε πριν δημοσιεύσετε. Το Juxt είναι μια υπηρεσία που είναι επίσης προσβάσιμη από το Διαδίκτυο, επομένως έχετε κατά νου ότι τα άτομα που δεν χρησιμοποιούν το Juxt μπορούν επίσης να δουν τις αναρτήσεις σας. Επιπλέον, τυχόν σχόλια που κάνετε στις αναρτήσεις των φίλων σας θα τα δουν όχι μόνο οι φίλοι σας αλλά και άνθρωποι σε όλο τον κόσμο. Παρακαλώ να το έχετε υπόψη σας.", "seventh": "Θυμηθείτε, το να γνωρίζετε κάποιον στο Juxt δεν είναι το ίδιο με το να τον γνωρίζετε στην πραγματική ζωή. Ποτέ μην μοιράζεστε τη διεύθυνση e-mail, τη διεύθυνση κατοικίας, το όνομα εργασίας ή σχολείου σας ή άλλες πληροφορίες προσωπικής ταυτοποίησης με κανέναν στο Juxt και ποτέ μην κοινοποιείτε τις πληροφορίες κανενός άλλου. Επιπλέον, αν κάποιος που συναντάτε στο Juxt σας προσκαλεί να τον γνωρίσετε στον πραγματικό κόσμο, μην το δεχθείτε. Το Juxt είναι μια διαδικτυακή κοινότητα και δεν πρέπει να χρησιμοποιείται για την οργάνωση πραγματικών συναντήσεων.", - "ninth": "Μερικοί άνθρωποι έρχονται στο Juxt αναζητώντας συμβουλές και κόλπα για παιχνίδια, αλλά άλλοι θέλουν να ανακαλύψουν τα μυστικά ενός παιχνιδιού μόνοι τους. Οι δημοσιεύσεις που αποκαλύπτουν μυστικά ενός παιχνιδιού ή της ιστορίας του ονομάζονται \"σπόιλερ\". Εάν δημοσιεύετε κάτι για ένα παιχνίδι που μπορεί να είναι spoiler, φροντίστε να ελέγξετε το πλαίσιο Spoilers πριν στείλετε τη δημοσίευσή σας. Με αυτόν τον τρόπο, τα άτομα που δεν θέλουν να κακομαθαίνουν δεν θα βλέπουν την ανάρτησή σας." + "ninth": "Μερικοί άνθρωποι έρχονται στο Juxt αναζητώντας συμβουλές και κόλπα για παιχνίδια, αλλά άλλοι θέλουν να ανακαλύψουν τα μυστικά ενός παιχνιδιού μόνοι τους. Οι δημοσιεύσεις που αποκαλύπτουν μυστικά ενός παιχνιδιού ή της ιστορίας του ονομάζονται «σπόιλερ». Εάν δημοσιεύετε κάτι για ένα παιχνίδι που μπορεί να είναι σπόιλερ, φροντίστε να ελέγξετε το πλαίσιο «Spoiler» πριν στείλετε τη δημοσίευσή σας. Με αυτόν τον τρόπο, τα άτομα που δεν θέλουν να κακομαθαίνουν δεν θα βλέπουν την ανάρτησή σας." }, "welcome": "Καλώς ήρθατε στο Juxtaposition!", - "info_text": "Το Juxt είναι μια κοινότητα παιχνιδιών με γνώμονα τον χρήστη, όπου μπορείτε να αλληλεπιδράσετε με ανθρώπους σε όλο τον κόσμο. Μπορείτε να γράφετε ή να σχεδιάζετε αναρτήσεις σε κοινότητες παιχνιδιών ή να στέλνετε μηνύματα απευθείας στους φίλους σας.", + "info_text": "Το Juxt είναι μια κοινότητα παιχνιδιών με γνώμονα τον χρήστη, όπου μπορείτε να αλληλεπιδράσετε με ανθρώπους\n από όλο τον κόσμο. Μπορείτε να γράφετε ή να σχεδιάζετε αναρτήσεις σε κοινότητες παιχνιδιών ή να στέλνετε μηνύματα απευθείας στους φίλους σας.", "welcome_text": "Το Juxt είναι μια gaming κοινότητα που συνδέει ανθρώπους από όλο τον κόσμο χρησιμοποιώντας χαρακτήρες Mii. Χρησιμοποιήστε το Juxt για να μοιραστείτε τις εμπειρίες σας στα παιχνίδια και να γνωρίσετε άτομα από όλο τον κόσμο.", - "google_text": "Το juxtaposition χρησιμοποιεί την υπηρεσία Google Analytics έτσι ώστε να συλλέγει πληροφορίες σχετικά με το πως οι χρήστες χρησιμοποιούν την υπηρεσία μας. Οι πληροφορίες περιλαμβάνουν τον χρόνο επίσκεψης ιστότοπων. Οι πληροφορίες δεν χρησιμοποιούνται εναντίων σας και δεν χρησιμοποιούνται για διαφημίσεις από εμάς ή από την Google.", + "google_text": "Το Juxtaposition χρησιμοποιεί την υπηρεσία Google Analytics έτσι ώστε να συλλέγει πληροφορίες σχετικά με το πως οι χρήστες χρησιμοποιούν την υπηρεσία μας. Οι πληροφορίες περιλαμβάνουν τον χρόνο επίσκεψης ιστότοπων. Οι πληροφορίες δεν χρησιμοποιούνται εναντίων σας και δεν χρησιμοποιούνται για διαφημίσεις από εμάς ή από την Google.", "experience": "Εμπειρία παιχνιδιού", "experience_text": { "info": "Πείτε μας πώς θα περιγράφατε το επίπεδο εμπειρίας σας με τα παιχνίδια. Μπορείτε να αλλάξετε αυτήν τη ρύθμιση αργότερα.", @@ -115,12 +115,12 @@ "expert": "Εξπέρ" }, "google": "Google Analytics", - "ready": "Έτοιμοι να ξεκινήσετε τη χρήση του Juxt", + "ready": "Είστε έτοιμοι να ξεκινήσετε τη χρήση του Juxt", "ready_text": "Πρώτα ελέγξτε μερικές κοινότητες και δείτε τι δημοσιεύουν άνθρωποι από όλο τον κόσμο. Εκμεταλλευτείτε αυτήν την ευκαιρία για να εξοικειωθείτε με το Juxt. Ίσως κάνετε κάποιες νέες ανακαλύψεις στην πορεία!", "done": "Καλή διασκέδαση στο Juxt!", "done_button": "Ας αρχίσουμε!", "guest": "Λειτουργία καλεσμένου", "guest_button": "Το κατάλαβα", - "guest_text": "Δεν μπορούμε να επαληθεύσουμε τον λογαριασμό σας αυτήν τη στιγμή, πράγμα που πιθανότατα σημαίνει ότι αυτή τη στιγμή συνδέεστε με λογαριασμό Nintendo Network. Θα εξακολουθείτε να μπορείτε να περιηγηθείτε στο Juxt, αλλά δεν θα μπορείτε να αλληλεπιδράσετε με Yeah! δημοσιεύετε ή κάνετε δικές σας αναρτήσεις μέχρι να συνδεθείτε με έναν λογαριασμό Pretendo Network. Για περισσότερες πληροφορίες, ελέγξτε τον διακομιστή μας στο Discord." + "guest_text": "Δεν μπορούμε να επαληθεύσουμε τον λογαριασμό σας αυτήν τη στιγμή, πράγμα που πιθανότατα σημαίνει ότι αυτή τη στιγμή συνδέεστε με λογαριασμό Nintendo Network. Θα εξακολουθείτε να μπορείτε να περιηγηθείτε στο Juxt, αλλά δεν θα μπορείτε να αλληλεπιδράσετε με Τέλειο!, δημοσιεύετε ή κάνετε δικές σας αναρτήσεις μέχρι να συνδεθείτε με έναν λογαριασμό Pretendo Network. Για περισσότερες πληροφορίες, ελέγξτε τον διακομιστή μας στο Discord." } } diff --git a/apps/juxtaposition-ui/src/translations/en@uwu.json b/apps/juxtaposition-ui/src/translations/en@uwu.json new file mode 100644 index 00000000..6c31694b --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/en@uwu.json @@ -0,0 +1,126 @@ +{ + "all_communities": { + "ann_string": "kik mii 4 nws fwom da big cheese!", + "popular_places": "so many kittehs in klans", + "new_communities": "new kitteh klans!!", + "text": "all kitteh klans", + "show_all": "show da secrets", + "search": "find kitteh klans…", + "announcements": "da big cheese nws!!" + }, + "community": { + "follow": "join kitteh klan", + "recent": "wecent p-posts", + "following": "Fowwowing", + "followers": "Fowwowers", + "popular": "Popuwaw P-posts", + "verified": "Vewwifiwed P-posts", + "new": "Nyew P-Post", + "posts": "", + "tags": "" + }, + "global": { + "communities": "kitteh klans", + "messages": "kitteh texts", + "activity_feed": "kitteh parti", + "go_back": "go bak", + "back": "bak", + "yeahs": "kitteh'd", + "more": "mor kittehs plz", + "private": "no kitteh alowd", + "save": "sav", + "exit": "kthxbai!!", + "close": "kitteh retret", + "next": "nex", + "user_page": "kitteh pege", + "notifications": "PINGS", + "no_posts": "no kittehs, sad :(" + }, + "language": "lolcat", + "user_page": { + "no_friends": "Nyo Fwiends", + "friends": "Fwends", + "posts": "P-posts", + "country": "Countwy", + "birthday": "Biwthday", + "game_experience": "G-G-Game Expewience", + "followers": "Fowwowers", + "following": "Fowwowing", + "follow_user": "Fowwow", + "following_user": "Fowwowing", + "befriend": "Befwiend", + "pending": "Pwending", + "unfriend": "Unfwiend", + "no_following": "Nyot Fowwowing Anyonye", + "no_followers": "" + }, + "setup": { + "rules_text": { + "eleventh": "", + "tenth": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "fourth": "", + "second": "", + "sixth": "", + "first": "", + "eighth": "", + "seventh": "", + "twelfth": "" + }, + "beta_text": { + "third": "", + "second": "", + "first": "" + }, + "experience_text": { + "expert": "", + "info": "", + "intermediate": "", + "beginner": "" + }, + "guest_button": "", + "done_button": "", + "guest": "", + "ready": "", + "guest_text": "", + "experience": "", + "google": "", + "rules": "", + "welcome_text": "", + "google_text": "", + "info": "", + "info_text": "", + "ready_text": "", + "done": "", + "beta": "", + "welcome": "" + }, + "notifications": { + "none": "", + "new_follower": "" + }, + "new_post": { + "swearing": "", + "text_hint": "", + "new_post_text": "", + "post_to": "" + }, + "user_settings": { + "show_game": "", + "show_comment": "", + "show_birthday": "", + "profile_settings": "", + "profile_comment": "", + "swearing": "", + "show_country": "" + }, + "activity_feed": { + "empty": "" + }, + "messages": { + "coming_soon": "" + } +} diff --git a/apps/juxtaposition-ui/src/translations/eo.json b/apps/juxtaposition-ui/src/translations/eo.json new file mode 100644 index 00000000..e3d4426c --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/eo.json @@ -0,0 +1,126 @@ +{ + "global": { + "user_page": "Profilo", + "activity_feed": "Novaĵoj", + "communities": "Komunumoj", + "messages": "Mesaĝoj", + "notifications": "Avizoj", + "go_back": "Reiri", + "back": "Reen", + "yeahs": "Ŝatoj", + "more": "Videbligi pli da afiŝoj", + "no_posts": "Neniuj afiŝoj", + "private": "Privata", + "close": "Fermi", + "save": "Konservi", + "exit": "Eliri", + "next": "Sekvanto" + }, + "language": "Esperanto", + "all_communities": { + "text": "Ĉiuj Komunumoj", + "announcements": "Anoncoj", + "ann_string": "Klaku ĉi tie, por vidi lastatempajn anoncojn!", + "new_communities": "Novaj Komunumoj", + "show_all": "Montri Ĉiun", + "search": "Serĉi Komunumojn...", + "popular_places": "Popularaj Komunumoj" + }, + "community": { + "followers": "Abonantoj", + "following": "Abonanta", + "follow": "Aboni Komunumon", + "posts": "Afiŝoj", + "tags": "Etikedoj", + "recent": "Lastatempaj Afiŝoj", + "popular": "Popularaj Afiŝoj", + "verified": "Kontrolitaj Afiŝoj", + "new": "Nova Afiŝo" + }, + "user_page": { + "country": "Lando", + "birthday": "Naskiĝtago", + "game_experience": "Ludosperto", + "posts": "Afiŝoj", + "friends": "Amikoj", + "following": "Abonanta", + "followers": "Abonantoj", + "follow_user": "Aboni", + "following_user": "Abonanta", + "befriend": "Amikiĝi", + "pending": "Atendanta", + "unfriend": "Malamikiĝi", + "no_friends": "Neniuj Amikoj", + "no_following": "Ne Abonas Iun", + "no_followers": "Neniuj Abonantoj" + }, + "user_settings": { + "profile_settings": "Profilagordoj", + "profile_comment": "Profilrimarko", + "show_country": "Montri Landon en Profilo", + "show_birthday": "Montri Naskiĝtagon en Profilo", + "show_game": "Montri Ludosperton en Profilo", + "swearing": "La profilrimarko ne povas enhavi maldecaĵojn.", + "show_comment": "Montri komenton en profilo" + }, + "activity_feed": { + "empty": "Malplenas ĉi tie. Provu aboni iun!" + }, + "notifications": { + "none": "Neniuj avizoj.", + "new_follower": "abonis vin!" + }, + "new_post": { + "new_post_text": "Novo Afiŝo", + "post_to": "Afiŝi al", + "text_hint": "Tuŝu ĉi tie por afiŝi.", + "swearing": "La afiŝo ne povas enhavi maldecaĵojn" + }, + "messages": { + "coming_soon": "Mesaĝoj ankoraŭ ne estas disponeblaj. Baldaŭ revenu!" + }, + "setup": { + "welcome": "Bonvenu en Juxtaposition!", + "beta": "Beta-disklerigo", + "welcome_text": "Juxt estas videoludado-komunumo kuniganta homojn el ĉiuj partoj de la mondo per Mii-figuroj. Uzu Juxt por diskonigi viajn ludadospertojn kaj renkontiĝi homojn el ĉiu angulo de la mondo.", + "beta_text": { + "first": "Vi tuj provas unuan publikan beton de Juxt. Tio signifas ke multe ankoraŭ ne estas klara, kaj iam ajn multe povas ŝanĝiĝi.", + "second": "Tio povas enteni totalan forviŝon de la datenbankon je la konludo de, aŭ dum la betoperiodo.", + "third": "La retejo, ĝia programaro kaj ĉiom da ĝia enhavo proviziĝas kiel ili estas kaj laŭ havebleco. La Pretendo-reto donas neniajn garantiojn, nek eksplicitajn nek implicitajn, pri la taŭgeco aŭ uzebleco de la retejo, ĝia programaro aŭ iom da ĝia enhavo." + }, + "rules_text": { + "third": "Juxt enhavas multajn videoludokomunumojn kie homoj de la tuta mondo povas dividi siajn pensojn. Afiŝante en komunumo, memoru ke ĉiu povas vidi ĝin, do bonvolu esprimi vin kiel ĉiu povas ĝui ĝin. Prudente agu, kaj pensu antaŭ ol vi afiŝas. Juxt estas medio ankaŭ disponebla per la Interreto, do konsideru ke neuzantoj de Juxt ankaŭ povus vidi viajn afiŝojn. Krome ĉiujn komentojn faritajn sub la afiŝoj de viaj amikoj ne nur viaj amikoj, sed ankaŭ homoj tra mondo vidos. Bonvolu pripensi ĉi tion.", + "second": "Afiŝoj Estas Videblaj Tra la Mondo", + "first": "Sekvante kelkaj gravaj gvidlinioj por ke Juxt estu amuza kaj ĝuable sperto por ĉiuj. La kodo de konduto de Juxt enhavas detalajn informojn, do bonvolu diligente legi.", + "fourth": "Estu Agrabla Unu al la Alia", + "fifth": "Por ke Juxt restu agrabla loko por ĉiu, ni petas ke vi estu konsiderema al aliaj uzantoj. Asistu nin restigi Juxt agrablan sperton per afiŝi nek maldecaĵojn nek ofendaĵojn.", + "sixth": "Ne afiŝu privatajn informojn, nek viajn, nek alies", + "ninth": "Kelkaj homoj venas al Juxt serĉante konsilojn kaj lertaĵojn por ludoj, sed aliaj volas malkovri la sekretojn de ludo tute memstare. Afiŝoj kiuj rivelas sekretojn de ludo aŭ ĝia rakonto estas nomitaj \"spoilers\". Se vi afiŝas ion pri ludo, kiu povus esti spoiler, nepre kontrolu la skatolon Spoilers antaŭ sendi vian afiŝon. Tiel homoj, kiuj ne volas esti difektitaj, ne vidos vian afiŝon.", + "eighth": "Ne Afiŝu Fimalkaŝojn", + "tenth": "Malrespekti la Kondutkodon", + "twelfth": "Ĉu Vi Ludis Tiun Ludon?", + "seventh": "Memoru, koni iun sur Juxt ne estas la sama kiel koni tiun en la reala vivo. Neniam dividu vian retpoŝtadreson, hejman adreson, laboran aŭ lernejan nomon, aŭ aliajn persone identigeblajn informojn kun iu ajn sur Juxt, kaj ankaŭ neniam dividu la informojn de iu alia. Aldone, se iu, kiun vi renkontas en Juxt, invitas vin renkonti tiun en la reala mondo, ne akceptu. Juxt estas interreta komunumo kaj ne estu uzata por aranĝi verajn kunvenojn.", + "eleventh": "Nia celo estas teni Juxt agrabla kaj ĝuebla por ĉiu. En la kazo ke iu malrespektas la Juxt-an Kondutkodon, ni adekvate agu, ĝis kaj inkluzive bari la malrespektintan uzanton aŭ konzolon.", + "thirteenth": "Se vi afiŝas en komunumo por ludo ludita de vi, viaj afiŝoj havos piktogramon indikantan ke vi jam ludis ĝin." + }, + "info": "Kio estas Juxtaposition?", + "info_text": "Juxt estas uzantobazita videoludokomunumo kie oni povas interagi kun homoj\n de la tuta mondo. Oni povas skribi aŭ desegni afiŝojn en videoludokomunumoj aŭ rekte mesaĝi al viaj amikoj.", + "rules": "reguloj de Juxt", + "google_text": "Juntapozicio uzas Google Analytics por spuri kiel uzantoj uzas nian servon. La datumoj kolektitaj inkluzivas sed ne estas limigitaj al tempo de vizito, paĝoj vizititaj kaj tempo pasigita en la retejo. Ĉi tiuj datumoj neniel estas ligitaj al vi, kaj ne estas uzataj por reklamoj de ni aŭ Guglo.", + "experience": "Ludosperto", + "experience_text": { + "expert": "Eksperto", + "beginner": "Komencanto", + "intermediate": "Mezo", + "info": "Bonvolu rakonti al ni kiel vi priskribus vian spertonivelon pri ludoj. Vi povas poste ŝanĝi tiun ĉi agordon." + }, + "google": "Google Analytics", + "ready_text": "Komence, miru kelkajn komunumojn kaj vidu pri kio afiŝadas homoj de la tuta mondo. Profitu ĉi tiun ŝancon por kutimiĝi kun Juxt. Eble kutimiĝante vi malkovrus ion aŭ iojn!", + "done": "Amuziĝu en Juxt!", + "ready": "Preta por Uzi Juxt", + "done_button": "Ni Iru!", + "guest": "Gastmodo", + "guest_button": "Mi Komprenas", + "guest_text": "Ni ĉi-momente ne povas konfirmi vian konton. Tio verŝajne signifas ke vi enŝultadas per Nintendo Network-konto. Vi povos paĝumi Juxt-e, sed vi ne povos Jes-i afiŝojn aŭ fari viajn proprajn afiŝojn ĝis vi enŝultas per Pretendo Network-konto. Por pli da informoj vidu en la Discord-servilo." + } +} diff --git a/apps/juxtaposition-ui/src/translations/es.json b/apps/juxtaposition-ui/src/translations/es.json index 2435cf69..c1e275f1 100644 --- a/apps/juxtaposition-ui/src/translations/es.json +++ b/apps/juxtaposition-ui/src/translations/es.json @@ -1,14 +1,14 @@ { "language": "Español", "global": { - "user_page": "Perfil", - "activity_feed": "Registro de actividad", + "user_page": "Página de usuario", + "activity_feed": "Actividad", "communities": "Comunidades", "messages": "Mensajes", "notifications": "Notificaciones", "go_back": "Volver", "back": "Atrás", - "yeahs": "Ajá", + "yeahs": "\"Mola\" concedidos", "more": "Cargar más publicaciones", "no_posts": "No hay publicaciones", "private": "Privado", @@ -22,7 +22,7 @@ "announcements": "Anuncios", "ann_string": "¡Toca para ver los anuncios recientes!", "popular_places": "Lugares populares", - "new_communities": "Nuevas comunidades", + "new_communities": "Comunidades nuevas", "show_all": "Mostrar todo", "search": "Buscar comunidades..." }, @@ -31,23 +31,23 @@ "following": "Siguiendo", "followers": "Seguidores", "posts": "Publicaciones", - "tags": "Etiquetas", - "recent": "Publicaciones recientes", - "popular": "Publicaciones populares", - "verified": "Publicaciones verificadas", + "tags": "Distintivos de uso", + "recent": "Mensajes recientes", + "popular": "Mensajes populares", + "verified": "Mensajes verificados", "new": "Nueva publicación" }, "user_page": { "country": "País", "birthday": "Cumpleaños", - "game_experience": "Experiencia jugando", + "game_experience": "Nivel como jugador", "posts": "Publicaciones", "friends": "Amigos", - "following": "Siguiendo", + "following": "Sigue a", "followers": "Seguidores", "follow_user": "Seguir", - "following_user": "Seguidores", - "befriend": "Ser amigos", + "following_user": "Sigue a", + "befriend": "Solicitud de amistad", "pending": "Pendiente", "unfriend": "Eliminar amigo", "no_friends": "Sin amigos", @@ -56,10 +56,10 @@ }, "user_settings": { "profile_settings": "Preferencias del perfil", - "show_country": "Mostrar país en tu perfil", - "show_birthday": "Mostrar cumpleaños en tu perfil", - "show_game": "Mostrar experiencia en tu perfil", - "show_comment": "Mostrar comentario en tu perfil", + "show_country": "Mostrar el país en el perfil", + "show_birthday": "Mostrar cumpleaños en el perfil", + "show_game": "Mostrar tu nivel como jugador en el perfil", + "show_comment": "Mostrar comentario en el perfil", "profile_comment": "Comentario del perfil", "swearing": "Los comentarios no pueden tener lenguaje inapropiado." }, @@ -81,14 +81,14 @@ }, "setup": { "welcome": "¡Bienvenido a Juxtaposition!", - "welcome_text": "Juxt es una comunidad de videojuegos para conectar a personas de todo el mundo usando personajes Mii. Juxt se puede usar para compartir tus experiencias Gaming y conocer personas de todo el mundo.", + "welcome_text": "Juxt es una comunidad de videojuegos para conectar a personas de todo el mundo usando personajes Mii. Juxt se puede usar para compartir tus experiencias de juego y conocer personas de todo el mundo.", "beta": "Información de la beta", "beta_text": { "first": "Estás a punto de probar la primera beta pública de Juxt. Esto significa que todavía quedan muchas cosas en el aire, y pueden haber grandes cambios en cualquier momento.", "second": "Esto puede incluir que todos los datos que almacenamos sean completamente eliminados durante o al acabar el periodo de beta.", "third": "El sitio web, su software y todo el contenido que se encuentra en él se proporcionan 'tal cual' y 'según esté disponible'. Pretendo Network no ofrece ninguna garantía, ya sea expresa o implícita, en cuanto a la integridad o usabilidad del sitio web, su software o cualquiera de sus contenidos." }, - "info": "¿Que es Juxtaposition?", + "info": "¿Qué es Juxtaposition?", "info_text": "Juxt es una comunidad de videojuegos dirigida por usuarios donde puedes interactuar con personas\n de todo el mundo. Puedes escribir y dibujar publicaciones en comunidades enfocadas a los videojuegos y enviar mensajes directamente a tus amigos.", "rules": "Reglas de Juxt", "rules_text": { @@ -99,16 +99,16 @@ "fifth": "Para que Juxt sea un lugar divertido para todos, te pedimos que seas considerado con los demás usuarios. Ayúdanos a que Juxt sea una experiencia agradable: no publiques nada inapropiado ni ofensivo.", "sixth": "No publiques información personal - tuya o de otras personas", "seventh": "Recuerda, conocer a alguien en Juxt no es lo mismo que conocerlo en la vida real. Nunca compartas tu dirección de correo electrónico, domicilio, nombre de tu trabajo o escuela u otra información de identificación personal con nadie en Juxt, y tampoco compartas la información de nadie más. Además, si alguien que conoces en Juxt te invita a conocerlo en el mundo real, no lo aceptes. Juxt es una comunidad en línea y no debe usarse para organizar reuniones en el mundo real.", - "eighth": "No publiques spoilers", - "ninth": "Algunas personas vienen a Juxt en busca de consejos y trucos para juegos, pero otras quieren descubrir los secretos de un juego por su cuenta. Las publicaciones que revelan secretos de un juego o su historia se denominan \"spoilers\". Si estás publicando algo sobre un juego que podría ser un spoiler, asegúrate de marcar la casilla \"Spoilers\" antes de enviar tu publicación. De esta forma, las personas que no quieran ver detalles del juego no verán tu publicación.", + "eighth": "No publiques revelaciones", + "ninth": "Algunas personas vienen a Juxt en busca de consejos y trucos para juegos, pero otras quieren descubrir los secretos de un juego por su cuenta. Las publicaciones que revelan secretos de un juego o su historia se denominan \"Revelaciones\". Si estás publicando algo sobre un juego que podría ser una revelación, asegúrate de marcar la casilla \"Revelaciones\" antes de enviar tu publicación. De esta forma, las personas que no quieran ver detalles del juego no verán tu publicación.", "tenth": "Violaciones del Código de conducta", "eleventh": "Nuestro objetivo es hacer que Juxt sea divertido y agradable para todos. En el caso de que alguien viole el Código de conducta de Juxt tomaremos las medidas adecuadas, incluyendo el bloqueo del usuario o la consola infractores.", "twelfth": "¿Has jugado a este juego?", "thirteenth": "Si publicas en una comunidad sobre algún juego que has jugado, tus publicaciones tendrán un icono que indica que has jugado a ese juego." }, - "experience": "Experiencia de juego", + "experience": "Nivel como jugador", "experience_text": { - "info": "Por favor, indica cuánta experiencia tienes jugando. Puedes cambiarlo después en Ajustes.", + "info": "Por favor, indica tu nivel como jugador. Puedes cambiarlo después en Ajustes.", "beginner": "Principiante", "intermediate": "Intermedio", "expert": "Experto" @@ -120,7 +120,7 @@ "done": "¡Que te diviertas en Juxt!", "done_button": "¡Vamos!", "guest": "Modo invitado", - "guest_text": "No podemos verificar tu cuenta en este momento, lo que probablemente significa que estás iniciando sesión con una cuenta de Nintendo Network. Podrás seguir navegando por Juxt, pero no podrás indicar ¡Genial! o crear publicaciones hasta que inicies sesión con una cuenta de Pretendo Network. Para obtener más información, consulta el servidor de Discord.", + "guest_text": "No podemos verificar tu cuenta en este momento, lo que probablemente significa que estás iniciando sesión con una cuenta de Nintendo Network. Podrás seguir navegando por Juxt, pero no podrás indicar ¡Mola! o crear publicaciones hasta que inicies sesión con una cuenta de Pretendo Network. Para obtener más información, consulta el servidor de Discord.", "guest_button": "Entendido" } } diff --git a/apps/juxtaposition-ui/src/translations/fr.json b/apps/juxtaposition-ui/src/translations/fr.json index 5e806591..4926ea95 100644 --- a/apps/juxtaposition-ui/src/translations/fr.json +++ b/apps/juxtaposition-ui/src/translations/fr.json @@ -1,104 +1,104 @@ { "language": "Français", "global": { - "user_page": "Profil", - "activity_feed": "Fil d'Actualité", + "user_page": "Page de l'utilisateur", + "activity_feed": "Suivi des activités", "communities": "Communautés", - "messages": "Messages", + "messages": "Messages privés", "notifications": "Notifications", "go_back": "Retour", - "back": "Retour", + "back": "Précédent", "yeahs": "Ouais", - "more": "Afficher Plus", - "no_posts": "Aucune Publication", - "private": "Privés", + "more": "Voir plus de publications", + "no_posts": "Aucune publication", + "private": "Privé", "close": "Fermer", "save": "Sauvegarder", "exit": "Quitter", "next": "Suivant" }, "all_communities": { - "text": "Toutes les Communautés", + "text": "Toutes les communautés", "announcements": "Annonces", - "ann_string": "Cliquez-ici pour voir les dernières annonces !", - "popular_places": "Lieux Populaires", - "new_communities": "Nouvelles Communautés", - "show_all": "Afficher Tout", - "search": "Rechercher des Communautés..." + "ann_string": "Cliquez ici pour voir les dernières annonces !", + "popular_places": "Populaires", + "new_communities": "Nouvelles communautés", + "show_all": "Tout afficher", + "search": "Rechercher des communautés..." }, "community": { - "follow": "Suivre la Communauté", - "following": "Abonné", + "follow": "Suivre la communauté", + "following": "Suivie", "followers": "Abonnés", "posts": "Publications", "tags": "Tags", - "recent": "Publications Récentes", - "popular": "Publications Populaires", - "verified": "Publications Vérifiées", - "new": "Nouvelle Publication" + "recent": "Posts récents", + "popular": "Posts populaires", + "verified": "Publications vérifiées", + "new": "Publier" }, "user_page": { "country": "Pays", "birthday": "Anniversaire", - "game_experience": "Expérience de Jeu", + "game_experience": "Expérience de joueur", "posts": "Publications", "friends": "Amis", "following": "Abonnements", "followers": "Abonnés", - "follow_user": "S'abonner", - "following_user": "Abonné", - "befriend": "Devenir Amis", - "pending": "En Attente", - "unfriend": "Retirer des Amis", - "no_friends": "Aucun Amis", - "no_following": "Ne Suit Personne", - "no_followers": "Aucun Abonnés" + "follow_user": "Suivre", + "following_user": "Suivi(e)", + "befriend": "Demander en ami", + "pending": "En attente", + "unfriend": "Retirer des amis", + "no_friends": "Aucun ami", + "no_following": "Ne suit aucun utilisateur", + "no_followers": "Aucun abonné" }, "user_settings": { - "profile_settings": "Paramètres du Profil", - "show_country": "Afficher le Pays sur le Profil", - "show_birthday": "Afficher l'Anniversaire sur le Profil", - "show_game": "Afficher l'Expérience de Jeu sur le Profil", - "show_comment": "Afficher les Commentaires sur le Profil", - "profile_comment": "Commentaires du Profil", - "swearing": "Les commentaires de profil ne peuvent pas contenir de language explicite." + "profile_settings": "Paramètres du profil", + "show_country": "Afficher mon pays sur le profil", + "show_birthday": "Afficher mon anniversaire sur le profil", + "show_game": "Afficher mon expérience de joueur sur le profil", + "show_comment": "Rendre visible le commentaire de mon profil", + "profile_comment": "Commentaire du profil", + "swearing": "Le commentaire du profil ne doit pas contenir de language explicite." }, "activity_feed": { - "empty": "C'est vide ici. Essaie de suivre quelqu'un !" + "empty": "C'est vide ici. Essaie de suivre quelqu'un !" }, "notifications": { - "none": "Aucune notifications.", - "new_follower": "vous suit !" + "none": "Aucune notification.", + "new_follower": "vous suit !" }, "new_post": { - "new_post_text": "Nouvelle Publication", + "new_post_text": "Nouvelle publication", "post_to": "Poster sur", - "text_hint": "Cliquez-ici pour poster un message.", + "text_hint": "Touchez ici pour commencer à poster.", "swearing": "Les publications ne peuvent pas contenir de language explicite." }, "messages": { - "coming_soon": "Les messages ne sont pas encore prêt pour le moment. Revenez bientôt !" + "coming_soon": "Les messages ne sont pas encore disponibles. Revenez bientôt !" }, "setup": { - "welcome": "Bienvenue sur Juxtaposition !", - "welcome_text": "Juxt est une communauté de jeu qui connecte les personnes du monde entier à l'aide de personnages Mii. Utilisez Juxt pour partager votre expériences de jeu et rencontrer les gens autour du globe.", - "beta": "Avertissement Beta", + "welcome": "Bienvenue sur Juxtaposition !", + "welcome_text": "Juxt est une communauté de jeu qui rassemble les personnes du monde entier à l'aide de personnages Mii. Utilisez Juxt pour partager votre expérience de jeu et rencontrer des gens autour du globe.", + "beta": "BÊTA : AVIS DE NON-GARANTIE", "beta_text": { - "first": "Vous êtes sur le point d'essayer la première version bêta publique de Juxt. Cela signifie que beaucoup de choses sont encore en suspens et qu'elles peuvent changer à tout moment.", + "first": "Vous êtes sur le point d'essayer la première version bêta publique de Juxt. Cela signifie que beaucoup de choses sont encore en développement et qu'elles peuvent changer à tout moment.", "second": "Cela peut inclure un effacement total de la base de données à la fin ou pendant la période bêta.", - "third": "Le site web, son logiciel et tout le contenu qui s'y trouve sont fournis sur une base \"telle quelle\" et \"telle que disponible\". Le Réseau Pretendo ne donne aucune garantie, expresse ou implicite, quant à l'adéquation ou à la facilité d'utilisation du site web, de son logiciel ou de son contenu." + "third": "Le site web, son logiciel et tout le contenu qui s'y trouve sont fournis « en l'état » et selon leur disponibilité. Le Pretendo Network ne fait aucune garantie, expresse ou implicite, quant à l'adéquation ou à la facilité d'utilisation du site web, de son logiciel ou de son contenu." }, - "info": "C'est quoi Juxtaposition ?", + "info": "C'est quoi, Juxtaposition ?", "info_text": "Juxt est une communauté de jeu gérée par les utilisateurs où vous pouvez interagir avec des personnes du monde entier.\nVous pouvez écrire ou dessiner des messages dans les communautés de jeu ou envoyer des messages directement à vos amis.", "rules": "Règles de Juxt", "rules_text": { - "first": "Voici quelques directives importantes pour faire de Juxt une expérience amusante et agréable pour tous. Le code de conduite de Juxt contient des informations détaillées. Veuillez les lires attentivement.", - "second": "Les publications peuvent être consultés dans le monde entier", - "third": "Juxt contient de nombreuses communautés de jeu où des personnes du monde entier peuvent partager leurs idées. Lorsque vous publiez un message dans une communauté, n'oubliez pas que tout le monde peut le voir, alors exprimez-vous de manière à ce que tout le monde puisse l'apprécier. Faites preuve de bon sens et réfléchissez avant de publier. Juxt est un service qui est également accessible sur Internet, alors n'oubliez pas que les personnes qui n'utilisent pas Juxt peuvent également voir vos messages. De plus, les commentaires que vous faites sur les messages de vos amis seront vus non seulement par vos amis mais aussi par des personnes autour du globe. Gardez cela à l'esprit.", + "first": "Voici quelques directives importantes pour faire de Juxt une expérience amusante et agréable pour tous. Le code de conduite de Juxt contient des informations détaillées. Veuillez les lire attentivement.", + "second": "Le monde entier pourra voir vos publications", + "third": "Juxt contient de nombreuses communautés de jeu où des personnes du monde entier peuvent partager leurs idées. Lorsque vous publiez du contenu dans une communauté, n'oubliez pas que tout le monde peut le voir, alors exprimez-vous de manière à ce que tout le monde puisse l'apprécier. Faites preuve de bon sens et réfléchissez avant de le publier. Juxt est un service qui est également accessible sur Internet, alors n'oubliez pas que les personnes qui n'utilisent pas Juxt peuvent également voir vos publications. De plus, les commentaires que vous enverrez sur les publications de vos amis seront visibles non seulement par vos amis mais aussi par des personnes autour du globe. Gardez cela à l'esprit.", "fourth": "Soyez gentils les uns envers les autres", "fifth": "Afin que Juxt reste un endroit agréable pour tous, nous vous demandons d'être soucieux des autres utilisateurs. Aidez-nous à faire de Juxt une expérience agréable en ne publiant rien d'inapproprié ou d'offensant.", "sixth": "Ne publiez pas d'informations personnelles - les vôtres ou celles des autres", - "seventh": "N'oubliez pas que connaître quelqu'un sur Juxt n'est pas la même chose que de le connaître dans la vie réelle. Ne partagez jamais votre adresse électronique, votre adresse personnelle, le nom de votre travail ou école, ou toute autre information permettant de vous identifier personnellement avec quelqu'un sur Juxt, et ne partagez jamais non plus les informations de quelqu'un d'autre. Si une personne rencontrée sur Juxt vous invite à la rencontrer dans le monde réel, n'acceptez pas. Juxt est une communauté en ligne et ne doit pas être utilisé pour organiser des rencontres dans le monde réel.", + "seventh": "N'oubliez pas que connaître quelqu'un sur Juxt n'est pas la même chose que de le connaître dans la vie réelle. Ne partagez jamais votre adresse électronique, votre adresse personnelle, le nom de votre lieu de travail ou école, ou toute autre information permettant de vous identifier personnellement avec quelqu'un sur Juxt, et ne partagez jamais non plus les informations de quelqu'un d'autre. Si une personne rencontrée sur Juxt vous invite à la rencontrer dans le monde réel, n'acceptez pas. Juxt est une communauté en ligne et ne doit pas être utilisé pour organiser des rencontres dans le monde réel.", "eighth": "Ne postez pas les secrets d'un jeu", "ninth": "Certaines personnes viennent sur Juxt à la recherche de conseils et d'astuces pour les jeux, mais d'autres veulent découvrir les secrets d'un jeu par elles-mêmes. Les messages qui révèlent les secrets d'un jeu ou de son histoire sont appelés \"spoilers\". Si vous publiez quelque chose sur un jeu qui pourrait être un spoiler, assurez-vous de cocher la case Spoilers avant d'envoyer votre message. Ainsi, les personnes qui ne veulent pas être spoilées ne verront pas votre message.", "tenth": "Violations du Code de Conduite", @@ -106,21 +106,21 @@ "twelfth": "Avez-vous joué à ce jeu ?", "thirteenth": "Si vous postez dans la communauté pour un jeu auquel vous avez joué, vos messages auront une icône indiquant que vous y avez joué." }, - "experience": "Expérience de Jeu", + "experience": "Expérience de joueur", "experience_text": { - "info": "Comment décririez-vous votre niveau de compétences avec les jeux ? Vous pourrez changer ce paramètre plus tard.", + "info": "Comment décririez-vous votre niveau de compétence avec les jeux ? Vous pourrez changer ce paramètre plus tard.", "beginner": "Débutant", "intermediate": "Connaisseur", "expert": "Adepte" }, "google": "Google Analytics", - "google_text": "Juxtaposition utilise Google Analytics afin de déterminer comment les utilisateurs utilisent notre service. Les données recueillies comprennent, sans s'y limiter, l'heure de la visite, les pages visitées et le temps passé sur le site. Ces données ne sont en aucun cas rattachées à votre personne et ne sont pas utilisées à des fins publicitaires par nous ou par Google.", + "google_text": "Juxtaposition utilise Google Analytics afin de savoir comment les visiteurs interagissent avec notre service. Les données recueillies comprennent, sans s'y limiter, l'heure de visite, les pages visitées et le temps passé sur le site. Ces données ne sont en aucun cas rattachées à votre personne et ne seront pas utilisées à des fins publicitaires, ni par nous ni par Google.", "ready": "Prêt à Utiliser Juxt", - "ready_text": "Commencez par consulter quelques communautés et voyez ce que les gens du monde entier publient. Profitez de cette occasion pour vous familiariser avec Juxt. Vous pourriez faire de nouvelles découvertes en cours de route !", - "done": "Amusez-vous bien sur Juxtaposition !", - "done_button": "C'est Parti !", + "ready_text": "Commencez par consulter quelques communautés et voyez ce que les gens du monde entier publient. Profitez de cette occasion pour vous familiariser avec Juxt. Vous pourriez faire de nouvelles découvertes en cours de route !", + "done": "Profitez bien de Juxt !", + "done_button": "C'est parti !", "guest": "Mode Invité", "guest_text": "Nous ne pouvons pas vérifier votre compte pour le moment, ce qui signifie probablement que vous vous connectez actuellement avec un compte Nintendo Network. Vous pourrez toujours naviguer sur Juxt, mais vous ne pourrez pas donner des \"Ouais!\" aux publications, ni créer les vôtres tant que vous ne vous serez pas connecté avec un compte Pretendo Network. Pour plus d'informations, consultez le Serveur Discord.", - "guest_button": "Je Comprends" + "guest_button": "Je comprends" } } diff --git a/apps/juxtaposition-ui/src/translations/fr_CA.json b/apps/juxtaposition-ui/src/translations/fr_CA.json new file mode 100644 index 00000000..ee901b6c --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/fr_CA.json @@ -0,0 +1,126 @@ +{ + "global": { + "user_page": "Profil", + "activity_feed": "Fil d'activité", + "communities": "Communautés", + "messages": "Messages", + "notifications": "Notifications", + "go_back": "Revenir", + "back": "Précédent", + "yeahs": "Ouais", + "more": "Charger plus de publications", + "no_posts": "Aucune publication", + "private": "Privé", + "close": "Fermer", + "save": "Sauvegarder", + "exit": "Sortir", + "next": "Suivant" + }, + "all_communities": { + "announcements": "Annonces", + "popular_places": "Communautés populaires", + "new_communities": "Nouvelles communautés", + "show_all": "Tout afficher", + "search": "Rechercher des communautés...", + "text": "Toutes les communautés", + "ann_string": "Cliquez ici pour voir les dernières annonces!" + }, + "community": { + "follow": "Suivre la communauté", + "following": "Suivie", + "followers": "Abonnés", + "tags": "Tags", + "posts": "Publications", + "recent": "Publi. récentes", + "popular": "Publi. populaires", + "verified": "Publi. vérifiées", + "new": "Publier" + }, + "user_page": { + "country": "Pays", + "birthday": "Anniversaire", + "game_experience": "Expérience de joueur", + "posts": "Publications", + "friends": "Amis", + "following": "Abonnements", + "followers": "Abonnés", + "follow_user": "Suivre", + "following_user": "Suivi(e)", + "befriend": "Demander en ami", + "pending": "En attente", + "unfriend": "Retirer des amis", + "no_friends": "Aucun ami", + "no_following": "Ne suit personne", + "no_followers": "Aucun abonné" + }, + "user_settings": { + "profile_settings": "Paramètres du profil", + "show_country": "Afficher mon pays sur le profil", + "show_birthday": "Afficher mon anniversaire sur le profil", + "show_game": "Afficher mon expérience de joueur sur le profil", + "show_comment": "Rendre visible le commentaire de mon profil", + "profile_comment": "Commentaire du profil", + "swearing": "Le commentaire du profil ne peut pas contenir de vocabulaire explicite." + }, + "activity_feed": { + "empty": "C'est vide ici. Essaie de suivre quelqu'un!" + }, + "notifications": { + "none": "Aucune notification.", + "new_follower": "vous suit!" + }, + "new_post": { + "new_post_text": "Nouvelle publication", + "post_to": "Poster à", + "text_hint": "Entrez ici le texte que vous allez publier.", + "swearing": "La publication ne peut pas contenir du vocabulaire explicite." + }, + "messages": { + "coming_soon": "Les messages ne sont pas encore disponibles. Revenez bientôt!" + }, + "setup": { + "welcome": "Bienvenue sur Juxtaposition!", + "welcome_text": "Juxt est une communauté de jeu qui rassemble du monde de partout à travers le monde en utilisant des personnages Mii. Utilisez Juxt pour partager votre expérience de jeu et rencontrer du monde à l'international.", + "beta": "Avertissement de version bêta", + "beta_text": { + "first": "Vous êtes sur le point d'essayer la première version bêta publique de Juxt. Ça veut dire que plusieurs choses sont encore en cours de développement et que tout peut changer à tout moment.", + "second": "Cela peut engendrer une suppression totale de la base de données pendant la période bêta ou à sa fin.", + "third": "Le site web, son logiciel et tout le contenu présent sont fournis comme tel et selon leur disponibilité. Le Pretendo Network ne fait aucune garantie, implicite ou explicite, quant à la pertinence ou au fonctionnement du site web, de son logiciel ou de son contenu." + }, + "info": "Qu'est-ce que Juxtaposition?", + "info_text": "Juxt est une communauté de jeu axée sur les utilisateurs où vous pouvez intéragir avec du monde de partout à travers le monde.\nVous pouvez écrire ou dessiner des publications dans des communautés de jeu ou envoyer des messages directement à vos amis.", + "rules": "Règles de Juxt", + "rules_text": { + "first": "Les directives suivantes sont importantes pour faire de Juxt une expérience amusante et agréable pour tous. Le code de conduite de Juxt contient des informations détaillées, donc veuillez les lire attentivement.", + "second": "Vos publications peuvent être vues partout à travers le monde", + "fourth": "Soyez gentils avec les autres", + "fifth": "Afin que Juxt reste un endroit agréable pour tous, nous vous demandons d'être soucieux des autres utilisateurs. Aidez-nous à faire de Juxt une expérience agréable en ne publiant rien d'inapproprié ou d'offensif.", + "sixth": "Ne publiez pas d'informations personnelles — Qu'elles soit les vôtres ou celles des autres", + "eighth": "Ne publiez pas de spoilers", + "tenth": "Violation du Code de Conduite", + "third": "Juxt héberge une panoplie de communautés de jeu dans lesquelles des personnes de partout sur la planète peuvent partager leurs idées. Lorsque vous publiez dans une communauté, souvenez-vous que tout le monde peut le voir, donc veuillez vous exprimer de façon convenable pour tout le monde. Prenez le temps de réfléchir et d'utiliser votre bon sens avant de publier quelque chose. Juxt est un service qui est aussi accessible en ligne, donc rappelez-vous que des personnes n'utilisant pas Juxt peuvent également voir vos publications. De plus, les commentaires que vous faites sur les publications de vos amis ne vont non seulement être vus par vos amis, mais également par des personnes du monde entier. Veuillez garder cela à l'esprit.", + "seventh": "Souvenez-vous que connaître quelqu'un sur Juxt n'est pas la même chose que de le connaître dans la vie réelle. Ne partagez jamais votre adresse courriel, où vous habitez, le nom de votre endroit de travail ou d'étude, ou toute autre information d'identification à qui que ce soit sur Juxt, et ne partagez jamais les informations des autres. De plus, si quelqu'un que vous rencontrez sur Juxt vous invite à les rencontrer en personne, n'acceptez pas son invitation. Juxt est une communauté virtuelle et ne devrait pas être utilisé pour planifier des rencontres dans le monde réel.", + "ninth": "Certains viennent sur Juxt afin de trouver des conseils et des astuces pour des jeux, mais d'autres veulent découvrir les secrets d'un jeu par eux-même. Les publications qui révèlent les secrets d'un jeu ou de son histoire sont appelés \"spoilers\". Si vous publiez quelque chose à propos d'un jeu qui pourrait être un spoiler, assurez-vous de cocher la case Spoilers avant de publier votre publication. Ainsi, le monde ne voulant pas être spoilé ne verra pas votre publication.", + "eleventh": "Notre objectif est de faire en sorte que Juxt soit amusant et agréable pour tout le monde. Au cas où quelqu'un enfreint le code de conduite de Juxt, nous allons prendre les mesures appropriées, pouvant aller jusqu'au blocage de l'utilisateur fautif ou de sa console.", + "twelfth": "As-tu joué à ce jeu?", + "thirteenth": "Si vous publier dans la communauté pour un jeu auquel tu as joué, tes publications aura une icône indiquant que vous l'avez joué." + }, + "experience": "Expérience de joueur", + "experience_text": { + "info": "S'il vous plait dites-nous comment décririez-vous votre niveau d'expérience avec les jeux. Tu peux changer ce paramètre plus tard.", + "beginner": "Débutant", + "intermediate": "Intermédiaire", + "expert": "Expert" + }, + "google": "Google Analytics", + "ready": "Prêt à commencer à utiliser Juxt", + "ready_text": "Consultez d’abord certaines communautés et voyez ce que les gens du tout le monde entier publient. Profitez de cette occasion pour vous familiariser avec Juxt. Vous ferez peut-être de nouvelles découvertes en cours de route !", + "done": "Amusez-vous dans Juxt!", + "done_button": "Allons-y!", + "guest": "Mode Invité", + "guest_button": "Je Comprends", + "guest_text": "Nous ne pouvons pas vérifier votre compte pour le moment, ce qui signifie probablement que vous êtes connecté avec un compte Nintendo Network. Vous pourrez toujours parcourir Juxt, mais vous ne pourrez pas accéder à Yeah! des publiez ou créez vos propres publications jusqu'à ce que vous vous connectiez avec un compte Pretendo Network. Pour plus d'informations, consultez le serveur Discord.", + "google_text": "Juxtaposition utilise Google Analytics afin de comprendre comment les utilisateurs utilisent notre service. Les données collectées incluent, sans toutefois s'y limiter, l'heure de visite, les pages visitées et le temps passé sur le site. Ces données ne sont en aucun cas associées à vous et ne seront pas utilisées à des fins publicitaires, ni par nous ni par Google." + }, + "language": "Français (Canada)" +} diff --git a/apps/juxtaposition-ui/src/translations/ga.json b/apps/juxtaposition-ui/src/translations/ga.json new file mode 100644 index 00000000..13555ae4 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/ga.json @@ -0,0 +1,126 @@ +{ + "global": { + "communities": "Pobail", + "messages": "Teachtaireachtaí", + "user_page": "Leathnach Úsáideora", + "go_back": "Téigh Ar Ais", + "activity_feed": "Fotha Gníomhaíochtaí", + "private": "Príobháideach", + "close": "Dún", + "exit": "Amach", + "more": "Lódáil Níos Mó", + "next": "Ar Aghaidh", + "notifications": "Fógraí", + "yeahs": "Sea", + "save": "Sábháil", + "back": "Ar Ais", + "no_posts": "Gan Postálacha" + }, + "all_communities": { + "announcements": "Fógraí Pobail", + "ann_string": "Cliceáil anseo chun na fograí is deanaí a fheceáil!", + "text": "Gach Phobail", + "popular_places": "Áiteanna bhfuil gnaoi an phobail air", + "new_communities": "Pobail Nua", + "show_all": "Taispeáin Gach Rud", + "search": "Cuardaigh pobail..." + }, + "community": { + "follow": "Lean Pobail", + "following": "Ag Leanúint", + "followers": "Leantóirí", + "posts": "Postálacha", + "tags": "Clibeanna", + "recent": "Postálacha Is Deanaí", + "verified": "Postálacha Deimhnithe", + "new": "Postáil Nua", + "popular": "Postálacha bhfuil gnaoi an phobail air" + }, + "user_page": { + "birthday": "Lá Breithe", + "game_experience": "Taithí Cluiche", + "posts": "Postálacha", + "friends": "Cairde", + "following": "Ag Leanúint", + "followers": "Leantóirí", + "follow_user": "Lean", + "following_user": "Ag Leanúint", + "befriend": "Cairde a deanamh leo", + "pending": "Ar Feitheamh", + "unfriend": "Stop ag bheith cairde leo", + "no_following": "Ag leanúint duine ar bith", + "no_followers": "Gan Leantóirí", + "country": "Tír", + "no_friends": "Gan Cairde" + }, + "user_settings": { + "show_country": "Taispeán Tír ar do Phroifíl", + "show_game": "Taispeán Taithí Cluiche ar do Phroifíl", + "show_birthday": "Taispeán Lá Breithe ar do Phroifíl", + "show_comment": "Taispeán Nóta Tráchta ar do Phrofíl", + "profile_comment": "Nóta Tráchta Proifíl", + "swearing": "Ní feidir le teanga follasach a bheith sa nóta tráchta proifíl.", + "profile_settings": "Socruithe Proifíl" + }, + "activity_feed": { + "empty": "Tá sé follamh anseo. Trial chun duine éigin a leanúint!" + }, + "notifications": { + "none": "Gan fógraí.", + "new_follower": "- lean siad tú!" + }, + "new_post": { + "post_to": "Postáil chuig", + "text_hint": "Tapáil anseo chun postáil a cruthú.", + "swearing": "Ní feidir le teanga follasach a bheith sa phostáil.", + "new_post_text": "Postáil Nua" + }, + "setup": { + "welcome": "Fáilte chuig Juxtaposition!", + "welcome_text": "Is pobail cluichíocht é Juxt a nascann daoine timpeall an domhain ag úsáid charactar Mii. Úsáid Juxt chun do taithí cluichíocht a roinnt agus bualadh le daoine timpeall an domhain.", + "beta": "Shéanadh Béite", + "beta_text": { + "first": "Tá tú chun an chead béite poiblí de Juxt a thrial amach. Cialaíonn sé seo go bhfuil rudaí fós neamhcinnte, agus is feidir le go leor athrú in ann tharlú ag aon am.", + "second": "Is féidir agus is féadfaidh go mbeidh scrios iomlán don bunachar sonraí ag deireadh nó i rith an tréimhse béite.", + "third": "" + }, + "info": "Cad é Juxtaposition?", + "rules_text": { + "second": "Is Feidir le Postálacha a Fheiceáil Ar Fud an Domhain", + "sixth": "Ná Postáil Eolas Pearsanta atá Leatsa nó Le Duine Eile", + "fourth": "Beidh Go Deas le Daoine Eile", + "third": "Tá go leor phobail cluchíocht i Juxt ina bhfuil daoine ar fud an domhain in ann a smaointe a roinnt. Nuair a cruthaíonn tú postáil, cuimhnigh gur feidir le gach daoine in ann é a fheiceál. Le do thoil, chur tú féin in iúl i mbealach a féidir le gach daoine taitneamh a bhaint as. Úsáid chiall cóiteann, agus smaoineamh roimh a deannan tú postál. Is seirbhís é Juxt a bhfuil ar fháil ar an tIdírlíon freisin. Cuimhnigh gur feidir freisin le dhaoine nach úsáideann Juxt do postálacha a fhecáil. Chomh maith leis sin, feicfear aon trácht a dheannan tú ar postálacha do chairde ag ní hamháin do chairde, ach daoine timpeall an domhain freisin. Le do thoil, cuimhnigh ar seo.", + "fifth": "Chun Juxt a coinnigh mar áit spraoiúil le haghadih gach duine, cuirimis ceist ort a bheidh tuisceanach le húsáideoirí eile. Cabhair linn Juxt a coinnigh mar taithí taitneamhach agus ná postáil rud ar bith atá mí-oiriúnach nó maslach.", + "eleventh": "", + "tenth": "", + "thirteenth": "", + "ninth": "", + "first": "", + "eighth": "", + "seventh": "", + "twelfth": "" + }, + "rules": "Rialacha Juxt", + "experience_text": { + "expert": "", + "info": "", + "intermediate": "", + "beginner": "" + }, + "guest_button": "", + "done_button": "", + "guest": "", + "ready": "", + "guest_text": "", + "experience": "", + "google": "", + "google_text": "", + "info_text": "", + "ready_text": "", + "done": "" + }, + "language": "Gaeilge", + "messages": { + "coming_soon": "Níl teachtaireachtaí réidh fós. Seiceáil ar ais go luath!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/gl.json b/apps/juxtaposition-ui/src/translations/gl.json new file mode 100644 index 00000000..c364c748 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/gl.json @@ -0,0 +1,126 @@ +{ + "global": { + "activity_feed": "Rexistro de actividades", + "communities": "Comunidades", + "messages": "Mensaxes", + "notifications": "Notificacións", + "go_back": "Volver", + "back": "De volta", + "private": "Privado", + "close": "Pechar", + "save": "Manteña", + "exit": "Saia", + "next": "Seguindo", + "user_page": "Páxina de usuario", + "yeahs": "Guay", + "more": "Cargar máis publicacións", + "no_posts": "Non hai publicacións" + }, + "all_communities": { + "text": "Todas as comunidades", + "announcements": "Anuncios", + "show_all": "Mostra todo", + "search": "Busca comunidades...", + "ann_string": "Fai clic para ver os últimos anuncios!", + "new_communities": "Novas comunidades", + "popular_places": "Lugares populares" + }, + "community": { + "follow": "Sigue a comunidade", + "following": "Continuar", + "followers": "Seguidores", + "posts": "Publicacións", + "tags": "Etiquetas", + "recent": "Mensaxes recentes", + "popular": "Publicacións populares", + "verified": "Publicacións verificadas", + "new": "Nova publicación" + }, + "user_page": { + "country": "Pais", + "game_experience": "Experiencia de xogo", + "posts": "Publicacións", + "friends": "Amigos", + "following": "Seguindo", + "followers": "Seguidores", + "follow_user": "Segue", + "birthday": "Aniversario", + "following_user": "Seguindo", + "befriend": "Engadir como amigo", + "pending": "Pendente", + "unfriend": "Borrar como amigo", + "no_friends": "Sen amigos", + "no_following": "Non Seguindo A Ninguén", + "no_followers": "Sen Seguidores" + }, + "language": "Galego", + "setup": { + "rules_text": { + "eleventh": "A nosa meta é facer que Juxt siga sendo divertido e disfrutáble para tod@s. No momento no que alguén viole o Código de Conducta de Juxt, tomaremos a acción apropiada, incluíndo bloquear o usuario ofensor ou consola.", + "twelfth": "Xogaches a Ise Xogo?", + "seventh": "Recorde que coñecer a alguén en Juxt non é o mesmo que coñecelo na vida real. Nunca compartas a túa direccion de e-mail, a dirección da túa casa, nome do traballo ou nome do colexio/instituto/universidade, ou outra información identificante con ninguén en Juxt, e nunca compartas a información de outras persoas tampouco. Adicionalmente, si alguén que coñeces en Juxt invítache a coñecerlle na vida real, non aceptes. Juxt é unha comunidade online e non debería ser usada para facer quedadas na vida real.", + "third": "Juxt contén moitas comunidades de xogadores onde a xente de todo o mundo pode compartir os seus pensamentos. Cando publica nunha comunidade, recorde que todo o mundo pode velo, así que por favor exprésese nunha maneira na que todos poidan disfrutar. Utilize o sentido común, e pense antes de publicar. Juxt é un serivizo que tamén é accesible desde o Internet, así que teña en mente que a xente que non usa Juxt pode que vexa as súas publicacións. Adicionalmente, calquer comentario que faga nas publicacións dos seus amigos van ser vistos por xente en todo o mundo tamén. Por favor, teña esto en mente.", + "first": "As seguintes son algunhas regras para facer Juxt un lugar divertido e disfrutáble para tod@s. O código de conducta de Juxt contén información detallada, así que por favor, léeo con atención.", + "thirteenth": "Se estás publicando algo na comunidade dun xogo que xogaches as túas publicacións terán unha ícona indicando que o xogaches.", + "second": "As Publicacións Poden Ser Vistas En Todo o Mundo", + "fourth": "Ser Agradables", + "fifth": "Para facer Juxt un lugar divertido para todo o mundo, pedímoslle que sexas amable aos outros usuarios. Axúdanos a facer que Juxt siga sendo un lugar disfrutáble non publicando nada inapropiado ou ofensivo.", + "sixth": "Non Publiques Información Persoal, Xa sexa túa ou de outros", + "eighth": "Non Subas Spoilers", + "tenth": "Violacións do Código de Conducta", + "ninth": "Algunhas persoas veñen a Juxt buscando pistas e trucos para xogos, pero outros queren descubrir os segredos dun xogo eles sós. As publicacións que revelen segredos dun xogo ou da súa historia chámanse \"spoilers\". Se vai a subir algo sobre un xogo que pode que sexa un spoiler, asegúrese de marcar a casilla de Spoilers antes de envíar a súa publicación. De esta maneira, a xente que non quere que lle fagan spoiler non verá a súa publicación." + }, + "google": "Analíticas de Google", + "done": "Pásao ben en Juxt!", + "done_button": "Vamos alá!", + "experience_text": { + "info": "Por favor cóntanos como describiría o seu nivel de experiencia de xogos. Podes cambiar iste axuste máis tarde.", + "beginner": "Principiante", + "expert": "Experto", + "intermediate": "Intermedio" + }, + "experience": "Experiencia de Xogo", + "ready_text": "Primeiro mira algunhas comunidades e mira sobre o que a xente de todo o mundo está publicando. Utilice esta oportunidade para familiarizarse con Juxt. Pode que fagas algún novo descubrimento polo camiño!", + "guest": "Modo Invitado", + "beta_text": { + "third": "A páxina web, o seu software e todo o contenido nela ofrécese \"tal cal\" e \"segundo a súa dispoñibilidade\". Pretendo Network non da ningunha garantía, xa sea expresado ou implicado, sobre a idoneidade ou usabilidade do sitio web, o seu software ou calquera do seu contido.", + "first": "Estás a punto de probar a primeira beta pública de Juxt. Esto significa que hai moitas cousas sen rematar, e moitas cousas poden cambiar en calquera momento.", + "second": "Esto pode e quizáis inclúa una eliminación da base de datos no final ou durante a fase beta." + }, + "ready": "Preparado Para Comezar a Usar Juxt", + "welcome": "Benvido a Juxtaposition!", + "beta": "Aviso De Versión Beta", + "info": "Que é Juxtaposition?", + "info_text": "Juxt é unha comunidade de xogadores manexada polos usuarios onde podes interactuar con xente\narredor do mundo. Podes escribir ou debuxar publicacións en comunidades de xogos ou enviar mensaxes directamente aos teus amigos (aínda non dispoñíbel).", + "rules": "Regras de Juxt", + "guest_text": "Non podemos verificar a súa conta en este momento, o que probablemente significa que vostede está iniciando a sesión con unha conta de Nintendo Network account. Vas a ser capaz de mirar Juxt, pero non poderá \"Yeah!\" publicacións e non facer as súas propias publicacións hasta que inicie a sesión con unha conta de Pretendo Network. Para máis información revise o servidor de Discord.", + "guest_button": "Entendóo", + "welcome_text": "Juxtaposition é unha comunidade de xogadores que conecta xente de todo o mundo usando persoaxes Mii. Usa Juxtaposition para compartir as túas experiencias xogando e coñece a xente de todo o mundo.", + "google_text": "Juxtaposition utiliza Google Analytics para ver como os usuarios utilizan o noso servicio. Os datos recollidos inclúen máis non limitan ao tempo de visita, páxinas visitadas e tempo pasado no sito. Estos datos non están unidos a vostede en ningunha maneira, e non está usada para anuncios nosos ou de Google." + }, + "new_post": { + "text_hint": "Toca aiquí para facer unha publicación.", + "new_post_text": "Nova Publicación", + "post_to": "Publicar a", + "swearing": "A publicación non pode ter linguaxe explícito." + }, + "user_settings": { + "show_comment": "Amosar Comentarios no Perfil", + "profile_settings": "Axustes Do Perfil", + "show_country": "Amosar País no Perfil", + "show_birthday": "Amosar Aniversario no Perfil", + "show_game": "Amosar Experiencia En Xogos no Perfil", + "profile_comment": "Comentario do Perfil", + "swearing": "O comentario do perfil non pode ter linguaxe explícito." + }, + "activity_feed": { + "empty": "Está baleiro aiquí. Proba a seguir a alguén!" + }, + "notifications": { + "none": "Sen notificacións.", + "new_follower": "seguíuche!" + }, + "messages": { + "coming_soon": "Está función non está preparada. Volve a mirar pronto!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/he.json b/apps/juxtaposition-ui/src/translations/he.json new file mode 100644 index 00000000..9ac9ca67 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/he.json @@ -0,0 +1,126 @@ +{ + "global": { + "user_page": "עמוד משתמש", + "communities": "קהילות", + "no_posts": "אין פוסטים", + "activity_feed": "עדכון פעילות", + "exit": "יְצִיאָה", + "next": "הַבָּא", + "messages": "הודעות", + "notifications": "התראות", + "go_back": "לַחֲזוֹר", + "back": "בְּחֲזָרָה", + "yeahs": "כן", + "more": "טען עוד פוסטים", + "private": "פְּרָטִי", + "close": "לִסְגוֹר", + "save": "לְהַצִיל" + }, + "all_communities": { + "text": "כל הקהילות", + "announcements": "הכרזות", + "ann_string": "לחץ כאן לצפייה בהכרזות האחרונות!", + "popular_places": "מקומות פופולריים", + "new_communities": "קהילות חדשות", + "show_all": "הצג הכל", + "search": "חפש קהילות..." + }, + "community": { + "new": "פוסט חדש", + "follow": "עקוב אחר הקהילה", + "following": "עוקבים", + "posts": "פוסטים", + "recent": "פוסטים אחרונים", + "popular": "פוסטים פופולריים", + "verified": "פוסטים מאומתים", + "tags": "", + "followers": "" + }, + "user_page": { + "birthday": "יוֹם הוּלֶדֶת", + "game_experience": "חווית משחק", + "posts": "פוסטים", + "friends": "חברים", + "following": "עוקבים", + "follow_user": "לַעֲקוֹב", + "following_user": "עוקבים", + "pending": "תָלוּי וְעוֹמֵד", + "no_friends": "אין חברים", + "no_following": "לא עוקב אחרי אף אחד", + "no_followers": "אין עוקבים", + "befriend": "", + "followers": "", + "unfriend": "", + "country": "" + }, + "user_settings": { + "profile_settings": "הגדרות פרופיל", + "show_birthday": "הצג יום הולדת בפרופיל", + "show_game": "", + "show_comment": "", + "profile_comment": "", + "swearing": "", + "show_country": "" + }, + "setup": { + "rules_text": { + "eleventh": "", + "tenth": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "fourth": "", + "second": "", + "sixth": "", + "first": "", + "eighth": "", + "seventh": "", + "twelfth": "" + }, + "beta_text": { + "third": "", + "second": "", + "first": "" + }, + "experience_text": { + "expert": "", + "info": "", + "intermediate": "", + "beginner": "" + }, + "guest_button": "", + "done_button": "", + "guest": "", + "ready": "", + "guest_text": "", + "experience": "", + "google": "", + "rules": "", + "welcome_text": "", + "google_text": "", + "info": "", + "info_text": "", + "ready_text": "", + "done": "", + "beta": "", + "welcome": "" + }, + "notifications": { + "none": "", + "new_follower": "" + }, + "new_post": { + "swearing": "", + "text_hint": "", + "new_post_text": "", + "post_to": "" + }, + "language": "", + "activity_feed": { + "empty": "" + }, + "messages": { + "coming_soon": "" + } +} diff --git a/apps/juxtaposition-ui/src/translations/hr.json b/apps/juxtaposition-ui/src/translations/hr.json new file mode 100644 index 00000000..7a25cc5f --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/hr.json @@ -0,0 +1,126 @@ +{ + "global": { + "go_back": "Idi natrag", + "back": "Natrag", + "yeahs": "Da", + "more": "Učitaj još objava", + "no_posts": "Nema objava", + "close": "Zatvori", + "save": "Spremi", + "exit": "Zatvori", + "private": "Privatno", + "next": "Dalje", + "activity_feed": "Kanal aktivnosti", + "user_page": "Korisnička stranica", + "communities": "Zajednice", + "messages": "Poruke", + "notifications": "Obavijesti" + }, + "all_communities": { + "text": "Sve zajednice", + "announcements": "Najave", + "ann_string": "Pritisni ovdje za prikaz nedavnih najava!", + "popular_places": "Popularna mjesta", + "new_communities": "Nove zajednice", + "show_all": "Pokaži sve", + "search": "Traži zajednice …" + }, + "community": { + "follow": "Prati zajednicu", + "tags": "Oznake", + "posts": "Objave", + "recent": "Nedavne objave", + "popular": "Popularane objave", + "verified": "Potvrđene objave", + "new": "Nove objave", + "following": "Praćenje", + "followers": "Sljedbenici" + }, + "user_page": { + "country": "Zemlja", + "birthday": "Rođendan", + "game_experience": "Iskustvo igranja", + "posts": "Objave", + "friends": "Prijatelji", + "follow_user": "Prati", + "unfriend": "Ukini prijateljstvo", + "no_friends": "Nema prijatelja", + "following": "Praćenje", + "following_user": "Praćenje", + "no_following": "Ne pratiš nikoga", + "followers": "Sljedbenici", + "no_followers": "Nema sljedbenika", + "befriend": "Sprijatelji se", + "pending": "Na čekanju" + }, + "user_settings": { + "profile_settings": "Postavke profila", + "show_country": "Prikaži zemlju u profilu", + "show_birthday": "Prikaži rođendan u profilu", + "show_game": "Prikaži iskustvo igranja u profilu", + "profile_comment": "Komentar profila", + "swearing": "Komentar profila ne smije sadržavati prostačke izraze.", + "show_comment": "Prikaži komentar u profilu" + }, + "notifications": { + "none": "Nema obavijesti.", + "new_follower": "te je pratio/la!" + }, + "new_post": { + "new_post_text": "Nova objava", + "post_to": "Objava za", + "swearing": "Objava ne smije sadržavati prostačke izraze.", + "text_hint": "Dodirni ovdje za izradu objave." + }, + "messages": { + "coming_soon": "Poruke još ne radi. Navrati kasnije!" + }, + "setup": { + "welcome": "Dobro došli u Juxtaposition!", + "beta": "Odricanje odgovornosti za beta izdanje", + "beta_text": { + "first": "Isprobavaš prvu javnu Juxt beta verziju. To znači da će vjerojatno biti još puno promjena.", + "second": "To može uključivati potpuno brisanje baze podataka na kraju ili tijekom razvoja beta verzije.", + "third": "Web stranica, softver i sav sadržaj pružaju se na osnovi „kao što je” i „kao što je dostupno”. Mreža Pretendo ne jamči, bilo izričito ili implicirano, prikladnost ili upotrebljivost web stranice, softvera ili bilo kojeg njenog sadržaja." + }, + "info": "Što je Juxtaposition?", + "welcome_text": "Juxt je zajednica za igranje koja povezuje ljude iz cijelog svijeta koristeći Mii likove. Koristi Juxt za dijeljenje tvojih iskustva igranja i upoznaj ljude iz cijelog svijeta.", + "info_text": "Juxt je korisnički vođena zajednica za igranje u kojoj možeš komunicirati s ljudima\n diljem svijeta. U zajednicama igara možeš pisati i crtati objave ili slati poruke izravno svojim prijateljima.", + "rules_text": { + "sixth": "Nemoj objavljivati osobne podatke – vlastite ili drugih osoba", + "third": "Juxt sadrži mnoge zajednice za igranje u kojima ljudi iz cijelog svijeta mogu podijeliti svoja razmišljanja. Kada šalješ obavijesti, imaj na umu da to svi mogu vidjeti, stoga se izrazi na pristojan način. Koristi zdrav razum i razmisli prije slanja objave. Juxt je usluga kojoj se također može pristupiti s interneta, stoga imaj na umu da osobe koje ne koriste Juxt također mogu vidjeti tvoje objave. Osim toga, svu tvoji komentari na objave tvojih prijatelja vidjet će ne samo tvoji prijatelji, već i ljudi diljem svijeta.", + "seventh": "Poznavati nekoga u Juxtu nije isto kao poznati osobu u stvarnom životu. Nikada nemoj dijeliti tvoju e-mail adresu, kućnu adresu, naziv posla ili škole ili druge osobne podatke s bilo kim na Juxtu. Također nemoj dijeliti tuđe podatke. Osim toga, ako te netko koga upoznaš u Juxtu pozove da ga upoznaš u stvarnom svijetu, nemoj prihvatiti poziv. Juxt je internetska zajednica i ne bi se trebala koristiti za organiziranje susreta u stvarnom svijetu.", + "eighth": "Nemoj objavljivati tajne igre", + "ninth": "Neki ljudi traže savjete i trikove za igre, drugi žele otkriti tajne igre samostalno. Objave koje otkrivaju tajne igre ili njezinu priču nazivaju se „spojleri”. Ako objavljuješ nešto o igri koja bi mogla biti spojler, prije slanja označi kvadratić spojlera. Na taj način, ljudi koji ne žele spojlere neće vidjeti tvoju objavu.", + "tenth": "Kršenja kodeksa ponašanja", + "eleventh": "Želimo da Juxt bude zabavan i ugodan za sve. U slučaju da netko prekrši kodeks ponašanja Juxta, poduzet ćemo odgovarajuće mjere, sve do blokiranja korisnika ili njegove konzole.", + "twelfth": "Jesi li igrao/la tu igru?", + "thirteenth": "Ako u zajednici objavljuješ igru koju si igrao/la, tvoje će objave dobiti ikonu koja ukazuje na to da si je igralo/la.", + "first": "Slijede neke važne smjernice za stvaranje zabavnog i ugodnog iskustva za sve. Kodeks ponašanja Juxta sadrži detaljne informacije, pa ih pažljivo pročitaj.", + "second": "Objave se mogu prikazati širom svijeta", + "fourth": "Budite uljudni jedni prema drugima", + "fifth": "Kako bismo održali zabavno mjesto za sve, budite uljudni jedni prema drugima. Pomognite nam zadržati ugodno iskustvo ne objavljujući ništa što je neprimjereno ili uvredljivo." + }, + "experience": "Iskustvo igranja", + "experience_text": { + "info": "Ocijeni tvoju razinu iskustva s igrama. Ovu postavku možeš kasnije promijeniti.", + "beginner": "Početnik", + "intermediate": "Napredni", + "expert": "Profesionalac" + }, + "google": "Google Analytics", + "google_text": "Juxtaposition koristi Google Analytics kako bi pratio način na koji korisnici koriste našu uslugu. Prikupljeni podaci uključuju, ali nisu ograničeni na vrijeme posjete, posjećene stranice i provedeno vrijeme na web stranici. Ovi podaci nisu na nikoji način s tobom povezani i mi niti Google ih ne koristimo za oglase.", + "ready": "Sve je spremno za početak korištenja Juxta", + "ready_text": "Najprije pogledaj neke zajednice i vidi što ljudi iz cijelog svijeta objavljuju. Iskoristi ovu priliku da upoznaš Juxt. Možda ćeš otkriti nešto novo!", + "done": "Uživaj u Juxtu!", + "done_button": "Krenimo!", + "guest": "Modus gosta", + "guest_text": "Trenutačno ne možemo provjeriti tvoj račun, što vjerojatno znači da se trenutačno prijavljuješ na Nintendo Network račun. I dalje ćeš moći pregledavati Juxt, ali nećeš moći odgovoriti na objave s „Da!” ili izraditi objave dok se ne prijaviš s Pretendo Network računom. Pregledaj Discord server za više informacija.", + "guest_button": "Razumijem", + "rules": "Juxt pravila" + }, + "language": "Hrvatski", + "activity_feed": { + "empty": "Ovdje je prazno. Pokušaj nekoga pratiti!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/hu.json b/apps/juxtaposition-ui/src/translations/hu.json new file mode 100644 index 00000000..86942f2b --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/hu.json @@ -0,0 +1,126 @@ +{ + "global": { + "user_page": "Profil", + "activity_feed": "Tevékenység csatorna", + "communities": "Közösségek", + "go_back": "Vissza", + "back": "Vissza", + "yeahs": "Yeahs", + "no_posts": "Nincs bejegyzés", + "private": "Privát", + "close": "Bezár", + "exit": "Kilépés", + "next": "Tovább", + "messages": "Üzenetek", + "more": "Több bejegyzés betöltése", + "save": "Mentés", + "notifications": "Értesítések" + }, + "all_communities": { + "ann_string": "Kattints ide a legfrissebb bejelentések megtekintéséhez!", + "popular_places": "Népszerű helyek", + "show_all": "Mindet mutatja", + "text": "Minden közösség", + "new_communities": "Új közösségek", + "announcements": "Bejelentések", + "search": "Közösségek keresése..." + }, + "community": { + "follow": "Közösség követése", + "following": "Követed", + "posts": "Bejegyzés", + "tags": "Címke", + "recent": "Legutolsó bejegyzések", + "followers": "Követő", + "popular": "Népszerű bejegyzések", + "verified": "Ellenőrzött bejegyzések", + "new": "Új bejegyzés" + }, + "user_page": { + "country": "Ország", + "game_experience": "Játék tapasztalat", + "posts": "Bejegyzések", + "friends": "Barátok", + "followers": "Követő", + "follow_user": "Követ", + "following_user": "Követve", + "befriend": "Barátnak jelöl", + "pending": "Folyamatban", + "unfriend": "Barátok közül töröl", + "no_friends": "Nincsennek barátok", + "no_following": "Nem követ senkit", + "no_followers": "Nincsennek követők", + "birthday": "Születésnap", + "following": "Követed" + }, + "user_settings": { + "show_country": "Ország megjelenítése a Profilon", + "show_birthday": "Születésnap megjelenítése a Profilon", + "profile_comment": "Profil megjegyzés", + "profile_settings": "Profil beállítások", + "show_game": "Játéktapasztalat megjelenítése a Profilon", + "show_comment": "Megjegyzés megjelenítése a Profilon", + "swearing": "A profil megjegyzés nem tartalmazhat csúnya beszédet." + }, + "notifications": { + "none": "Nincsennek értesítések.", + "new_follower": "követ téged!" + }, + "new_post": { + "new_post_text": "Új bejegyzés", + "post_to": "Bejegyzés ehhez:", + "swearing": "A bejegyzés nem tartalmazhat csúnya beszédet.", + "text_hint": "Koppints ide egy új bejegyzés létrehozásához." + }, + "messages": { + "coming_soon": "Az Üzenetek még nincs kész. Nézz vissza később!" + }, + "setup": { + "welcome": "Köszöntünk Juxtaposition-ben!", + "beta": "Béta Jogi nyilatkozat", + "beta_text": { + "first": "A Juxt első publikus bétáját fogod kipróbálni. Ez azt jelenti, hogy egy csomó minden lóg a levegőben, és változhat idővel.", + "third": "A weboldal, a szoftver és mindent tartalom “as is” és “as available” alapon biztosított. A Pretendo Network nem biztosít semmilyen garanciát, akár közvetlen, akár beleértett, a megfelelőségéről és használhatóságáról a weboldalnak a szoftvernek, vagy bármilyen tartalomnak.", + "second": "Ez jelentheti az adatbázis teljes törlését valamilyen döntést követően vagy a béta periódus végén." + }, + "info": "Mi az a Juxtaposition?", + "info_text": "A Juxt egy felhasználó alapú játék közösség, ahol kapcsolatba léphetsz emberekkel\n a világ minden tájáról. Írhatsz és rajzolhatsz bejegyzéseket a játék közösségekbe, vagy küldhetsz közvetlen üzenetet barátaidnak.", + "rules": "Juxt szabályok", + "rules_text": { + "second": "A bejegyzéseket az egész világon megtekinthetik", + "fourth": "Legyetek kedvesek egymáshoz", + "fifth": "Annak érdekében, hogy a Juxt mindenki számára szórakoztató hely maradjon, kérjük, légy figyelmes más felhasználókkal szemben. Segíts nekünk abban, hogy a Juxt élvezetes élmény legyen azáltal, hogy nem teszel közzé semmi nem helyénvalót vagy sértőt.", + "sixth": "Ne posztolj személyes információt - se sajátodat, se másokét", + "tenth": "Magatartási kódex megsértése", + "eighth": "Ne posztolj spoilereket", + "eleventh": "Célunk, hogy a Juxt szórakoztató és élvezetes legyen mindenki számára. Abban az esetben, ha valaki megsérti a Juxt magatartási kódexét, megtesszük a megfelelő lépéseket, beleértve a jogsértő felhasználó vagy konzol letiltását is.", + "twelfth": "Játszottál már azzal a játékkal?", + "thirteenth": "Ha a közösségbe posztolod egy játékról, hogy játszottad, a posztjaid tartalmazni fognak egy ikont, hogy játszottál vele.", + "seventh": "Ne feledd, hogy valakit a Juxt-ban ismerni nem ugyanaz, mint a való életben. Soha ne oszd meg e-mail címed, otthoni címed, munkahelyed vagy iskolád nevét vagy más személyazonosításra alkalmas adatod senkivel a Juxt-on, és soha ne oszd meg más adatait sem. Továbbá, ha valaki, akivel a Juxt-ban találkozol, meghív, hogy találkozz vele a való világban, ne fogadd el. A Juxt egy online közösség, és nem javasolt valós találkozók szervezésére használni.", + "ninth": "Vannak, akik azért jönnek a Juxt-hoz, hogy tippeket és trükköket keressenek a játékokhoz, de mások egyedül szeretnék felfedezni egy játék titkait. Azokat a bejegyzéseket, amelyek egy játék vagy annak történetének titkait fedik fel, „spoilereknek” nevezzük. Ha egy játékról teszel közzé valamit, ami spoiler lehet, a bejegyzés elküldése előtt feltétlenül jelöld be a \"spoilers\" négyzetet. Így azok, akik nem akarnak spoilert látni, nem fogják látni a bejegyzésedet.", + "first": "Az alábbiakban felsorolunk néhány fontos irányelvet, amelyekkel a Juxt mindenki számára szórakoztató és élvezetes élmény lehet. A Juxt magatartási kódexe részletes információkat tartalmaz, ezért kérjük, figyelmesen olvassa el azt.", + "third": "A Juxt számos játékközösséget tartalmaz, ahol az emberek a világ minden tájáról megoszthatják gondolataikat. Amikor közzé teszel valamit egy közösségben, ne feledd, hogy mindenki láthatja, ezért kérjük, fejezd ki magad úgy, hogy az mindenki számára élvezetes legyen. Használd a józan eszedet, és gondolkozz, mielőtt posztolsz. A Juxt egy olyan szolgáltatás, amely az internetről is elérhető, ezért ne feledd, hogy azok is láthatják a bejegyzéseid, akik nem használják a Juxt-ot. Ezenkívül az ismerőseid bejegyzéseihez írt megjegyzéseid nemcsak az ismerőseid, hanem a világ minden tájáról. Kérjük, tartsd ezt szem előtt." + }, + "experience": "Játék tapasztalat", + "experience_text": { + "info": "Kérjük írd le magadról hogy milyen szinten van tapasztalatod a játékokkal. Módosíthatod ezt később.", + "beginner": "Kezdő", + "intermediate": "Középszint", + "expert": "Profi" + }, + "google": "Google Analytics", + "ready": "Készen állsz használni a Juxt-ot", + "ready_text": "Először nézz meg néhány közösséget, és nézd meg, miről posztolnak az emberek a világ minden tájáról. Használd ki az alkalmat, hogy megismerkedj a Juxttal. Útközben új felfedezéseket tehetsz!", + "done": "Jó szórakozást a Juxt-ban!", + "done_button": "Kezdjük!", + "guest": "Vendég mód", + "guest_text": "Jelenleg nem tudjuk ellenőrizni fiókod, ami valószínűleg azt jelenti, hogy egy Nintendo Network fiókkal vagy bejelentkezve. Továbbra is böngészhetsz a Juxtban, de nem fogsz tudni a Yeah! bejegyzéseket vagy saját bejegyzéseket készíteni, amíg be nem jelentkezel egy Pretendo Network fiókkal. További információkért nézd meg a Discord szervert.", + "guest_button": "Megértettem", + "welcome_text": "A Juxt egy játék közösség, ami összeköti az embereket a világ minden tájáról Mii karakterek használatával. Használd a Juxt-ot, hogy megoszd játék tapasztalataid és találkozz emberekkel a világ minden tájáról.", + "google_text": "A Juxtaposition a Google Analytics szolgáltatást használja annak nyomon követésére, hogy a felhasználók hogyan használják szolgáltatásunkat. Az összegyűjtött adatok magukban foglalják, de nem kizárólagosan a látogatás idejét, a meglátogatott oldalakat és az oldalon töltött időt. Ezeket az adatokat semmilyen módon nem kapcsoljuk Önhöz, és sem mi, sem a Google nem használja fel hirdetésekhez." + }, + "activity_feed": { + "empty": "Nagyon üres itt. Próbálj meg valakit követni!" + }, + "language": "Magyar" +} diff --git a/apps/juxtaposition-ui/src/translations/id.json b/apps/juxtaposition-ui/src/translations/id.json new file mode 100644 index 00000000..155b01ca --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/id.json @@ -0,0 +1,126 @@ +{ + "global": { + "user_page": "Halaman Pengguna", + "communities": "Komunitas", + "activity_feed": "Halaman Aktivitas", + "messages": "Pesan-pesan", + "notifications": "Notifikasi", + "go_back": "Kembali", + "back": "Kembali", + "yeahs": "Ya", + "more": "Memuat Postingan", + "no_posts": "Tidak ada Postingan", + "private": "Pribadi", + "close": "Tutup", + "save": "Simpan", + "exit": "Keluar", + "next": "Berikut" + }, + "language": "Bahasa Indonesia", + "all_communities": { + "show_all": "Tunjuk Semua", + "text": "Semua Komunitas", + "announcements": "Pengumuman", + "ann_string": "Klik sini untuk melihat pengumuman terkini!", + "popular_places": "Tempat Populer", + "new_communities": "Komunitas Baru", + "search": "Cari Komunitas..." + }, + "user_settings": { + "show_game": "Tunjuk Pengalaman Permainan di Profil", + "show_comment": "Tunjuk Komentar di Profil", + "profile_comment": "Komentar Profil", + "profile_settings": "Pengaturan Profil", + "show_country": "Tunjuk Negara di Profil", + "show_birthday": "Tunjuk Ulang Tahun di Profil", + "swearing": "Komentar Profil tidak boleh mengandung bahasa eksplisit." + }, + "setup": { + "welcome_text": "Juxt adalah komunitas game yang menghubungan orang-orang dari seluruh dunia menggunakan karakter Mii. Gunakan Juxt untuk berbagi pengalaman bermain game anda dan bertemu orang-orang dari seluruh dunia.", + "beta_text": { + "first": "Anda akan mencoba versi beta publik pertama Juxt. Artinya, masih banyak hal yang belum terealisasi, dan banyak hal yang bisa berubah kapan saja.", + "second": "Hal ini dapat dan mungkin mencakup penghapusan total basis data para akhir atau selama periode beta.", + "third": "Situs web, perangkat lunaknya, dan semua konten yang terdapat di dalamnya disediakan atas dasar \"apa adanya\" dan \"sebagaimana tersedia\". Pretendo Network tidak memberikan jaminan apa pun, baik tersurat maupun tersirat, mengenai kesesuaian atau kegunaan situs web, perangkat lunaknya, atau konten apa pun di dalamnya." + }, + "welcome": "Selamat datang ke Juxtaposition (Juxt)!", + "beta": "Pembertitahuan Beta", + "done_button": "Ayo!", + "guest_button": "Saya Mengerti", + "experience": "Pengalaman Permainan", + "rules_text": { + "first": "Berikut ini adalah beberapa aturan penting untuk menjadikan Juxt sebagai pengalaman yang menyenangkan dan mengasyikkan bagi semua orang. Kode Etik Juxt memuat informasi terperinci, jadi harap baca dengan saksama.", + "third": "Juxt berisi banyak komunitas game tempat orang-orang dari seluruh dunia dapat berbagi pemikiran mereka. Saat anda memposting di suatu komunitas, ingatlah bahwa semua orang dapat melihatnya, jadi harap ekspresikan diri anda dengan cara yang dapat dinikmati semua orang. Gunakan akal sehat, dan pikirkan sebelum anda memposting. Juxt adalah layanan yang juga dapat diakses dari Internet, jadi perlu diingat bahwa orang yang tidak menggunakan Juxt juga dapat melihat postingan Anda. Selain itu, komentar apa pun yang anda buat pada postingan teman-teman anda tidak hanya akan dilihat oleh teman-teman anda, tetapi juga oleh orang-orang di seluruh dunia. Harap diingat hal ini.", + "sixth": "Jangan Memposting Informasi Pribadi—Informasi Anda atau Informasi Orang Lain", + "eighth": "Jangan Postingan Spoiler", + "eleventh": "Tujuan kami adalah menjaga Juxt tetap menyenangkan dan menghibur bagi semua orang. Jika ada yang melanggar Kode Etik Juxt, kami akan mengambil tindakan yang sesuai, termasuk memblokir pengguna atau konsol yang melanggar.", + "thirteenth": "Jika Anda mengepos di komunitas tentang permainan yang pernah Anda mainkan, pos Anda akan memiliki ikon yang menunjukkan bahwa Anda telah memainkannya.", + "fourth": "Bersikap Baik Terhadap Satu Sama Lain", + "seventh": "Ingat, mengenal seseorang di Juxt tidak sama dengan mengenalnya di dunia nyata. Jangan pernah membagikan alamat email, alamat rumah, nama kantor atau sekolah, atau informasi pengenal pribadi lainnya dengan siapa pun di Juxt, dan jangan pernah membagikan informasi orang lain juga. Selain itu, jika seseorang yang anda temui di Juxt mengundang Anda untuk bertemu dengannya di dunia nyata, jangan terima. Juxt adalah komunitas daring dan tidak boleh digunakan untuk mengatur pertemuan di dunia nyata.", + "second": "Postingan Dapat Dilihat di Seluruh Dunia", + "fifth": "Agar Juxt tetap menjadi tempat yang menyenangkan bagi semua orang, kami meminta anda untuk bersikap baik kepada pengguna lain. Bantu kami agar Juxt tetap menjadi pengalaman yang menyenangkan dengan tidak mengunggah sesuatu yang tidak pantas atau menyinggung.", + "tenth": "Pelanggaran Kode Etik", + "ninth": "Beberapa orang datang ke Juxt untuk mencari tips dan trik permainan, tetapi yang lain ingin menemukan sendiri rahasia permainan. Postingan yang mengungkap rahasia permainan atau ceritanya disebut \"spoiler.\" Jika anda memposting sesuatu tentang permainan yang mungkin merupakan spoiler, pastikan untuk mencentang kotak \"Spoiler\" sebelum mengirim postingan anda. Dengan cara ini, orang yang tidak ingin mengetahui spoiler tidak akan melihat postingan anda.", + "twelfth": "Pernahkah Anda Memainkan Game Itu?" + }, + "rules": "Aturan Juxt", + "experience_text": { + "info": "Harap beri tahu kami bagaimana Anda menggambarkan tingkat pengalaman Anda dengan gim. Anda dapat mengubah pengaturan ini nanti.", + "intermediate": "Intermediat", + "expert": "Pakar", + "beginner": "Pemula" + }, + "ready_text": "Pertama, kunjungi beberapa komunitas dan lihat apa yang diposting orang-orang dari seluruh dunia. Manfaatkan kesempatan ini untuk mengenal Juxt lebih jauh. Anda mungkin akan menemukan beberapa hal baru di sepanjang perjalanan!", + "done": "Bersenang-senanglah di Juxt!", + "google": "Google Analytics", + "ready": "Siap untuk Mulai Menggunakan Juxt", + "guest": "Mode Tamu", + "info_text": "Juxt adalah komunitas game yang digerakkan oleh pengguna tempat anda dapat berinteraksi dengan orang-orang\n di seluruh dunia. Anda dapat menulis atau menggambar postingan di komunitas game atau mengirim pesan langsung ke teman-teman anda.", + "guest_text": "Kami tidak dapat memverifikasi akun anda saat ini, yang kemungkinan berarti Anda saat ini masuk dengan akun Nintendo Network. Anda masih dapat menjelajahi Juxt, tetapi anda tidak akan dapat memposting Ya! atau membuat postingan sendiri hingga anda masuk dengan akun Pretendo Network. Untuk informasi lebih lanjut, lihat di server Discord.", + "google_text": "Juxt menggunakan Google Analytics untuk melacak bagaimana pengguna menggunakan layanan kami. Data yang dikumpulkan termasuk tetapi tidak terbatas pada waktu kunjungan, halaman yang dikunjungi, dan waktu yang dihabiskan di situs. Data ini tidak dikaitkan dengan Anda dengan cara apa pun, dan tidak digunakan untuk iklan oleh kami atau Google.", + "info": "Apa itu Juxtaposition?" + }, + "community": { + "follow": "Ikut Komunitas", + "following": "Mengikuti", + "followers": "Pengikutas", + "posts": "Postingan", + "tags": "Label", + "recent": "Postingan Terkini", + "popular": "Postingan Populer", + "verified": "Postingan Diverifikasi", + "new": "Postingan Baru" + }, + "user_page": { + "country": "Negara", + "birthday": "Ulang Tahun", + "game_experience": "Pengalaman Permainan", + "posts": "Postingan", + "friends": "Teman-Teman", + "following": "Mengikuti", + "followers": "Pengikutas", + "follow_user": "Mengikut", + "following_user": "Mengikuti", + "befriend": "Nambah Teman", + "pending": "Tertunda", + "unfriend": "Hapus Teman", + "no_friends": "Tidak ada Teman", + "no_following": "Tidak Mengikuti Orang", + "no_followers": "Tidak Ada Pengikutas" + }, + "activity_feed": { + "empty": "Tidak ada apa-apa di sini. Coba mengikut orang!" + }, + "notifications": { + "none": "Tidak ada notificasi.", + "new_follower": "diikuti kamu!" + }, + "new_post": { + "new_post_text": "Postingan Baru", + "post_to": "Posting ke", + "text_hint": "Klik sini untuk membuat postingan.", + "swearing": "Postingan tidak boleh mengandung bahasa eksplisit." + }, + "messages": { + "coming_soon": "Pesan-Pesan belum siap sekarang. Segara periksa kembali!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/it.json b/apps/juxtaposition-ui/src/translations/it.json index 69bfc2ef..c95ca699 100644 --- a/apps/juxtaposition-ui/src/translations/it.json +++ b/apps/juxtaposition-ui/src/translations/it.json @@ -2,14 +2,14 @@ "language": "Italiano", "global": { "user_page": "Pagina dell'utente", - "activity_feed": "Feed delle attività", - "communities": "Community", + "activity_feed": "Attività", + "communities": "Comunità", "messages": "Messaggi", "notifications": "Notifiche", "go_back": "Torna indietro", "back": "Indietro", "yeahs": "Sìì", - "more": "Carica più post", + "more": "Carica altri post", "no_posts": "Nessun post", "private": "Privato", "close": "Chiudi", @@ -18,18 +18,18 @@ "next": "Avanti" }, "all_communities": { - "text": "Tutte le community", + "text": "Tutte le comunità", "announcements": "Annunci", "ann_string": "Clicca qui per vedere gli annunci più recenti!", "popular_places": "Luoghi popolari", - "new_communities": "Nuove community", + "new_communities": "Nuove comunità", "show_all": "Mostra tutto", - "search": "Cerca community..." + "search": "Cerca comunità..." }, "community": { - "follow": "Segui community", - "following": "Stanno seguendo", - "followers": "Follower", + "follow": "Segui comunità", + "following": "Seguiti", + "followers": "Seguaci", "posts": "Post", "tags": "Etichette", "recent": "Post recenti", @@ -43,16 +43,16 @@ "game_experience": "Esperienza di gioco", "posts": "Post", "friends": "Amici", - "following": "Sta seguendo", - "followers": "Follower", + "following": "Seguiti", + "followers": "Seguaci", "follow_user": "Segui", "following_user": "Stai seguendo", "befriend": "Chiedi amicizia", "pending": "In attesa", "unfriend": "Rimuovi amicizia", "no_friends": "Nessun amico", - "no_following": "Non segue nessuno", - "no_followers": "Nessun follower" + "no_following": "Non segui nessuno", + "no_followers": "Nessun seguace" }, "user_settings": { "profile_settings": "Impostazioni del profilo", @@ -80,7 +80,7 @@ "coming_soon": "La funzionalità di messaggistica non è ancora pronta. Ricontrolla presto!" }, "setup": { - "welcome": "Benvenuto su Juxtaposition!", + "welcome": "Benvenuto/a su Juxtaposition!", "welcome_text": "Juxt è una community di gioco che connette persone da tutto il mondo attraverso personaggi Mii. Usa Juxt per condividere le tue esperienze di gioco e incontrare persone da tutto il mondo.", "beta": "Disclaimer beta", "beta_text": { @@ -116,7 +116,7 @@ "google": "Google Analytics", "google_text": "Juxtaposition utilizza Google Analytics per tracciare il modo in cui gli utenti utilizzano il nostro servizio. I dati raccolti includono ma non si limitano a: momento della visita, pagine visitate e tempo passato sul sito. Questi dati non sono ricollegabili a te in alcun modo, e non sono utilizzati per finalità pubblicitarie da noi o Google.", "ready": "Pronto a iniziare a usare Juxt", - "ready_text": "Per primaa cosa dai un'occhiata a qualche community e scopri di cosa stanno parlando persone da tutto il mondo. Cogli questa opportunità per familiarizzarti con Juxt. Potresti fare nuove scoperte strada facendo!", + "ready_text": "Per prima cosa dai un'occhiata a qualche community e scopri di cosa stanno parlando persone da tutto il mondo. Cogli questa opportunità per familiarizzarti con Juxt. Potresti fare nuove scoperte strada facendo!", "done": "Divertiti in Juxt!", "done_button": "Andiamo!", "guest": "Modalità ospite", diff --git a/apps/juxtaposition-ui/src/translations/ja.json b/apps/juxtaposition-ui/src/translations/ja.json index d5998a67..812f647e 100644 --- a/apps/juxtaposition-ui/src/translations/ja.json +++ b/apps/juxtaposition-ui/src/translations/ja.json @@ -1,28 +1,28 @@ { "language": "日本語", "global": { - "user_page": "マイメニュー", + "user_page": "ユーザーページ", "activity_feed": "みんなの活動", "communities": "コミュニティ", "messages": "メッセージ", - "notifications": "おしらせ", - "go_back": "もどる", - "back": "もどる", + "notifications": "お知らせ", + "go_back": "戻る", + "back": "戻る", "yeahs": "そうだね", - "more": "もっと読み込む", + "more": "さらに読み込む", "no_posts": "投稿がありません", "private": "非公開", - "close": "おわる", + "close": "閉じる", "save": "保存", "exit": "閉じる", "next": "次へ" }, "all_communities": { "text": "すべてのコミュニティ", - "announcements": "おしらせ", - "ann_string": "最新のお知らせはこちら!", - "popular_places": "人気コミュニティ", - "new_communities": "新着コミュニティ", + "announcements": "お知らせ", + "ann_string": "最新のお知らせはこちら!", + "popular_places": "人気のコミュニティ", + "new_communities": "新しいコミュニティ", "show_all": "すべて見る", "search": "コミュニティをさがす..." }, @@ -32,9 +32,9 @@ "followers": "フォロワー", "posts": "投稿", "tags": "タグ", - "recent": "新しい投稿", + "recent": "最近の投稿", "popular": "人気の投稿", - "verified": "プレイ済みの投稿", + "verified": "確認済みの投稿", "new": "投稿する" }, "user_page": { @@ -51,7 +51,7 @@ "pending": "承認待機中", "unfriend": "フレンドから外す", "no_friends": "フレンドがいません", - "no_following": "フォローがいません", + "no_following": "誰もフォローしていません", "no_followers": "フォロワーがいません" }, "user_settings": { @@ -61,7 +61,7 @@ "show_game": "プロフィールにゲームの腕前を表示", "show_comment": "プロフィールに自己紹介を表示", "profile_comment": "自己紹介", - "swearing": "自己紹介に悪口を含めることはできません。" + "swearing": "自己紹介に悪口を含めないでください。" }, "activity_feed": { "empty": "空っぽです。誰かをフォローしてみましょう!" @@ -74,13 +74,13 @@ "new_post_text": "投稿する", "post_to": "次に投稿する:", "text_hint": "ここをタップして投稿します。", - "swearing": "投稿に露骨な表現を含めることはできません。" + "swearing": "投稿に露骨な表現を含めないでください。" }, "messages": { - "coming_soon": "メッセージはまだ準備ができていません。すぐにもう一度チェックしてください!" + "coming_soon": "メッセージはまだ準備ができていません。すぐにもう一度確認してください!" }, "setup": { - "welcome": "Juxtapositionへようこそ!", + "welcome": "Juxtapositionへようこそ!", "welcome_text": "Juxtは、Miiキャラクターを使って世界中の人々をつなぐゲームコミュニティです。 Juxt を使用して、ゲーム体験を共有し、世界中の人々と出会いましょう。", "beta": "ベータ版免責事項", "beta_text": { @@ -88,7 +88,7 @@ "second": "これはベータ期間中や終了後、データベースの完全な消去に繋がることがあります。", "third": "このウェブサイト、そのソフトウェア及びその中のすべてのコンテンツは「そのまま」、「利用できる状態で」提供されます。Pretendo Networkは、ウェブサイト、ソフトウェア及びその中のコンテンツの適切性や有用性についていかなる明示的、黙示的保証もしません。" }, - "info": "Juxtってなんですか?", + "info": "Juxtとは?", "info_text": "Juxtは世界中の人々と交流できる、ユーザー主導のゲームコミュニティです。ゲームコミュニティで\n 文字や絵を用いて投稿をしたり、直接友達とメッセージ交換ができます。", "rules": "Juxtのルール", "rules_text": { diff --git a/apps/juxtaposition-ui/src/translations/kk.json b/apps/juxtaposition-ui/src/translations/kk.json index 0967ef42..0a50a266 100644 --- a/apps/juxtaposition-ui/src/translations/kk.json +++ b/apps/juxtaposition-ui/src/translations/kk.json @@ -1 +1,126 @@ -{} +{ + "language": "қазақша", + "global": { + "go_back": "Артқа", + "more": "Қосымша хабарламаларды жүктеңіз", + "yeahs": "Иә", + "user_page": "Пайдаланушы беті", + "activity_feed": "Белсенділік арнасы", + "communities": "Қауымдастықтар", + "messages": "Хабарламалар", + "notifications": "Хабарландырулар", + "back": "Артқа", + "no_posts": "Посттар жоқ", + "private": "Жеке", + "close": "Жабу", + "save": "Сақтаңыз", + "exit": "Шығу", + "next": "Келесі" + }, + "all_communities": { + "text": "Барлық қауымдастықтар", + "announcements": "Хабарландырулар", + "ann_string": "Соңғы хабарландыруларды көру үшін осы жерді басыңыз!", + "popular_places": "Танымал орындар", + "new_communities": "Жаңа қауымдастықтар", + "show_all": "Барлығын көрсетіңіз", + "search": "Қауымдастықтарды іздеу..." + }, + "community": { + "follow": "Қауымдастықты бақылаңыз", + "following": "Төменде", + "followers": "Ізбасарлар", + "posts": "Хабарламалар", + "tags": "Тегтер", + "recent": "Соңғы жазбалар", + "popular": "Танымал жазбалар", + "verified": "Тексерілген хабарламалар", + "new": "Жаңа пост" + }, + "user_page": { + "country": "Ел", + "birthday": "Туған күн", + "game_experience": "Ойын тәжірибесі", + "friends": "Достар", + "posts": "Жазбалар", + "follow_user": "", + "no_following": "", + "befriend": "", + "following_user": "", + "pending": "", + "followers": "", + "unfriend": "", + "no_followers": "", + "no_friends": "", + "following": "" + }, + "setup": { + "experience_text": { + "info": "Ойындар бойынша тәжірибе деңгейіңізді қалай сипаттайтыныңызды айтыңыз. Бұл параметрді кейінірек өзгертуге болады.", + "intermediate": "Орташа", + "expert": "", + "beginner": "" + }, + "google": "Google Analytics", + "rules_text": { + "eighth": "Спойлер жазбаңыз", + "eleventh": "", + "tenth": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "fourth": "", + "second": "", + "sixth": "", + "first": "", + "seventh": "", + "twelfth": "" + }, + "ready": "Juxt пайдалануды бастауға дайын", + "guest_button": "Мен түссіндім", + "beta_text": { + "third": "", + "second": "", + "first": "" + }, + "done_button": "", + "guest": "", + "guest_text": "", + "experience": "", + "rules": "", + "welcome_text": "", + "google_text": "", + "info": "", + "info_text": "", + "ready_text": "", + "done": "", + "beta": "", + "welcome": "" + }, + "notifications": { + "none": "", + "new_follower": "" + }, + "new_post": { + "swearing": "", + "text_hint": "", + "new_post_text": "", + "post_to": "" + }, + "user_settings": { + "show_game": "", + "show_comment": "", + "show_birthday": "", + "profile_settings": "", + "profile_comment": "", + "swearing": "", + "show_country": "" + }, + "activity_feed": { + "empty": "" + }, + "messages": { + "coming_soon": "" + } +} diff --git a/apps/juxtaposition-ui/src/translations/ko.json b/apps/juxtaposition-ui/src/translations/ko.json index 254f0775..4d0363fa 100644 --- a/apps/juxtaposition-ui/src/translations/ko.json +++ b/apps/juxtaposition-ui/src/translations/ko.json @@ -8,9 +8,9 @@ "notifications": "알림", "go_back": "뒤로 가기", "back": "뒤로", - "yeahs": "맞네", - "more": "추가 포스트 로드", - "no_posts": "포스트 없음", + "yeahs": "당근이지", + "more": "게시물 더보기", + "no_posts": "게시물 없음", "private": "비공개", "close": "닫기", "save": "저장", @@ -21,90 +21,90 @@ "text": "모든 커뮤니티", "announcements": "공지", "ann_string": "여기를 클릭해서 최근 공지를 확인하세요!", - "popular_places": "유명한 곳", - "new_communities": "새로운 커뮤니티", - "show_all": "모두 보이기", + "popular_places": "인기 커뮤니티", + "new_communities": "새 커뮤니티", + "show_all": "모두 보기", "search": "커뮤니티 검색..." }, "community": { - "follow": "커뮤니티 팔로우", - "following": "팔로잉", + "follow": "커뮤니티 팔로우하기", + "following": "팔로우 중", "followers": "팔로워", - "posts": "포스트", + "posts": "게시물", "tags": "태그", - "recent": "최근 포스트", - "popular": "인기 포스트", - "verified": "인증된 포스트", - "new": "새로운 포스트" + "recent": "최근 게시물", + "popular": "인기 게시물", + "verified": "인증된 게시물", + "new": "새 게시물" }, "user_page": { "country": "국가", "birthday": "생년월일", "game_experience": "게임 경험", - "posts": "포스트", + "posts": "게시물", "friends": "친구", - "following": "팔로잉", + "following": "팔로우 중", "followers": "팔로워", "follow_user": "팔로우", "following_user": "팔로우 중", "befriend": "친구 추가하기", - "pending": "대기중", + "pending": "대기 중", "unfriend": "친구 제거하기", "no_friends": "친구 없음", - "no_following": "팔로우하는 사람 없음", + "no_following": "팔로우 중인 사람 없음", "no_followers": "팔로워 없음" }, "user_settings": { "profile_settings": "프로필 설정", - "show_country": "프로필에 국가 포시", + "show_country": "프로필에 국가 표시", "show_birthday": "프로필에 생년월일 표시", - "show_game": "프로필에 게임 경혐 표시", + "show_game": "프로필에 게임 경험 표시", "show_comment": "프로필에 메시지 표시", "profile_comment": "프로필 메시지", - "swearing": "프로필 메시지는 노골적인 단어를 포함할 수 없습니다." + "swearing": "프로필 메시지는 비속어를 포함할 수 없습니다." }, "activity_feed": { - "empty": "아무것도 없네요. 누군가를 팔로우해 보세요!" + "empty": "아무도 없네요. 누군가를 팔로우 해 보세요!" }, "notifications": { - "none": "알림은 없어요.", - "new_follower": "가 당신을 팔로우했습니다!" + "none": "알림 없음.", + "new_follower": "(이)가 당신을 팔로우했어요!" }, "new_post": { - "new_post_text": "새 포스트", - "post_to": "여기로 게시하기’", - "text_hint": "여기를 눌러서 포스트를 만드세요.", - "swearing": "포스트는 노골적인 단어를 포함할 수 없습니다." + "new_post_text": "새 게시물", + "post_to": "여기로 게시하기", + "text_hint": "여기를 눌러 새 게시물을 작성하세요.", + "swearing": "게시물에는 비속어를 사용 할 수 없습니다." }, "messages": { "coming_soon": "응 아직 개발 안됨 ㅅㄱ 나중에 다시 와 보세요!" }, "setup": { "welcome": "Juxtaposition에 오신 것을 환영합니다!", - "welcome_text": "Juxt는 Mii 캐릭터를 사용하여 전 세계의 사람들을 잇는 게이밍 커뮤니티입니다. Juxt를 사용해서 게임 경험을 나누고, 전 세계 사람들과 만나세요.", + "welcome_text": "Juxt는 Mii 캐릭터를 사용하여 전 세계의 사람들을 잇는 게이밍 커뮤니티입니다. Juxt를 사용해서 게임 경험을 나누고, 전 세계의 사람들과 소통하세요.", "beta": "베타 테스트 주의", "beta_text": { "first": "당신은 Juxt의 첫 공개 베타에 참가한다는 것을 잊지 마세요. 아직 버그들이 많을 것이며, 언제든지 모든 게 확 바뀔 수 있습니다.", - "second": "베타 기간 중 및 종료 후, 온 데이터베이스가 삭제될 수 있습니다.", - "third": "이 웹사이트 및 그 소프트웨어, 그 외 모든 컨텐츠는 “있는 그대로” 및 “사용 가능한 상태로” 제공됩니다. Pretendo Network는 어떠한 웹사이트, 소프트웨어 또는 콘텐츠의 적합성 및 유용성에 대해 어떠한 명시적·묵시적 보증도 하지 않습니다." + "second": "베타 기간 중 혹은 종료 후에는 전체 데이터베이스가 삭제 될 수도 있습니다.", + "third": "이 웹 사이트 및 그 소프트웨어, 그 외 모든 컨텐츠는 “있는 그대로” 및 “사용 가능한 상태로” 제공됩니다. Pretendo Network는 어떠한 웹사이트, 소프트웨어 또는 콘텐츠의 적합성 및 유용성에 대해 어떠한 명시적·묵시적 보증도 하지 않습니다." }, "info": "Juxtaposition가 무엇이죠?", - "info_text": "Juxt는 당신이 전 세계의 사람들과 상호작용할 수 있는, 유저에 의해 운영되고 있는\n 게임 커뮤니티입니다. 게임 커뮤니티 내에서 포스트를 작성하거나, 친구에게 직접 메시지를 보낼 수 있습니다.", - "rules": "Juxt 규칙", + "info_text": "유저들이 직접 운영하는 Juxt는 당신이 전 세계의 사람들과 상호 작용할 수 있는\n 게임 커뮤니티입니다. 게임 커뮤니티 내에서 게시물을 작성하거나, 혹은 친구에게 직접 메시지를 보낼 수도 있습니다.", + "rules": "Juxt의 규칙", "rules_text": { - "first": "이하 내용은 Juxt를 모두가 재미있고 편안한 커뮤니티로 만들 수 있기에 앞서, 중요한 내용들입니다. Juxt 행동 강령에는 각 항목에 대해 보다 자세한 내용이 적혀 있으므로, 참고하며 조심히 읽어 주시기 바랍니다.", - "second": "포스트는 전 세계에서 볼 수 있다", - "third": "Juxt는 전 세계 사람들이 생각을 나눌 수 있는 게임 커뮤니티가 아주 많이 포함되어 있습니다. 커뮤니티에 포스트를 작성할 시, 모두가 볼 수 있다는 것을 잊지 마시고, 모두가 즐길 수 있는 방식으로 표현해 주시기 바랍니다. 상식적인 포스트를 작성해 주시기 바라며, 게시 전에 반드시 다시 한번 더 고려해 주시기 바랍니다. Juxt는 인터넷에서도 액세스가 가능하므로, Juxt를 사용하지 않는 사람들도 당신의 포스트를 볼 수 있다는 사실을 기억해 주시기 바랍니다. 추가적으로, 당신이 친구의 포스트에 단 댓글도 친구 뿐만 아니라, 전 세계의 분들도 열람할 수 있습니다. 마음에 새겨 주시기 바랍니다.", - "fourth": "타인에게 잘 하자", - "fifth": "Juxt를 모두에게 즐거운 곳으로 하기 위해, 당신이 다른 유저들에게 사려 깊게 행동해 주시는 것을 부탁드립니다. 부적절하거나, 공격적인 내용은 게시를 하지 말아 주세요.", - "sixth": "자신이든 타인이든, 개인 정보를 게시하지 말자’", + "first": "이하 내용은 Juxt를 모두가 재미있고 편안한 커뮤니티로 만들기 위한 중요한 내용들입니다. Juxt의 규칙에는 각 항목에 대해 보다 자세한 내용이 적혀 있으므로, 참고하며 조심히 읽어 주시기 바랍니다.", + "second": "게시물은 전 세계에서 볼 수 있습니다", + "third": "Juxt는 전 세계 사람들이 생각을 나눌 수 있는 게임 커뮤니티가 아주 많이 존재합니다. 커뮤니티에 게시물을 작성할 시, 모두가 볼 수 있다는 것을 잊지 마시고, 모두가 즐길 수 있는 방식으로 표현해 주시기 바랍니다. 상식적인 게시물들을 작성해 주시기 바라며, 게시 전에 반드시 다시 한번 더 고려해 주시기 바랍니다. 또한 Juxt는 인터넷에서도 액세스가 가능하므로, Juxt를 사용하지 않는 사람들도 당신의 게시물을 볼 수 있다는 사실을 기억해 주시길 바랍니다. 그리고 또, 당신이 친구의 게시물에 단 댓글도 친구 뿐만 아니라, 전 세계의 모든 사람들이 다 볼 수 있습니다. 어떻게 해서든 꼭 기억해주시길 바랍니다.", + "fourth": "다른 사람을 잘 대해주세요", + "fifth": "Juxt를 모두가 다 같이 즐거울 수 있는 곳으로 만들기 위해서, 다른 사용자들에게 사려 깊게 행동해 주시는 것을 부탁드립니다. 부적절하거나, 공격적인 내용은 게시를 하지 말아 주세요.", + "sixth": "아무리 자신의 개인정보라도 게시하지 마세요", "seventh": "명심해 주세요, Juxt에서 누군가를 아는 것은 실제 생활에서 누군가를 아는 것이랑 전혀 다릅니다. 절대로 메일 주소, 집 주소, 회사 및 학교명, 타 개인 정보를 Juxt의 누구와도 공유하지 마시고, 타인의 정보도 보내지 마시기 바랍니다. 추가적으로, 만약 Juxt에서 만난 누군가가 실제 생활에서 만나자고 했다면, 승탁하지 마세요. Juxt는 온라인 커뮤니티이며, 실제 생활에서의 미팅을 만드는 용도로 쓰이지 말아야 합니다.", - "eighth": "스포일러는 게시하지 말자", - "ninth": "몇몇 사람들은 게임 내 팁 등을 찾으러 Juxt에 오지만, 다른 사람들은 게임 시크릿을 직접 찾고 싶을 겁니다. 게임 내 시크릿이나 스토리를 밝히는 포스트는 \"스포일러\"라고 불립니다. 만약 게임과 관련된, 스포가 될 만한 정보를 게시하려고 하고 있다면, 포스트를 게시하기 전에 스포일러 박스를 체크하는 것을 잊지 마세요. 이렇게 하면, 스포당하고 싶지 않은 사람들은 당신의 포스트를 보지 않을 겁니다.", - "tenth": "행동 강령 위반", - "eleventh": "저희의 목표는 Juxt가 모두가 재미있고 즐길 수 있는 커뮤니티임을 유지하는 것입니다. 만약 누군가가 Juxt 행동 강령을 위반했다면, 저희가 적절하다고 판단한 조치를 취할 것입니다. 잘못하면 그 유저나 콘솔을 차단할 수도 있죠.", + "eighth": "스포일러는 게시하지 말아주세요", + "ninth": "몇몇 사람들은 게임 내 팁 등을 찾으러 Juxt에 오지만, 다른 사람들은 게임의 비밀을 직접 찾고 싶을 겁니다. 게임 내 비밀이나 스토리를 밝히는 게시물을 \"스포일러\"라고 불립니다. 만약 게임과 관련된, 스포가 될 만한 정보를 게시하려고 하고 있다면, 게시물을 게시하기 전에 스포일러 박스를 체크하는 것을 잊지 마세요. 이렇게 하면, 스포당하고 싶지 않은 사람들은 당신의 게시물을 보지 않을 겁니다.", + "tenth": "규칙 위반", + "eleventh": "저희의 목표는 Juxt가 모두가 재미있고 즐길 수 있는 커뮤니티임을 유지하는 것입니다. 만약 누군가가 Juxt의 규칙을 위반했다면, 저희가 적절하다고 판단한 조치를 취할 것입니다. 잘못하면 그 유저나 콘솔을 차단할 수도 있죠.", "twelfth": "그 게임 해봤어?", - "thirteenth": "만약 당신이 해본 게임의 커뮤니티에 포스트를 작생했을 시, 포스트에 당신이 플레이했다는 아이콘이 뜰 것입니다." + "thirteenth": "만약 당신이 해본 게임의 커뮤니티에 게시물을 작성하면, 작성한 게시물에 이 게임을 플레이 했다는 아이콘이 뜰 것입니다." }, "experience": "게임 경험", "experience_text": { @@ -116,11 +116,11 @@ "google": "Google Analytics", "google_text": "Juxtaposition은 사용자들이 어떻게 저희의 서비스를 활용하고 있는지 트래킹하기 위해 Google Analytics를 사용합니다. 수집하고 있는 데이터는 방문 횟수, 방문 페이지, 사이트에서 보낸 시간 등을 포함합니다. 이 데이터는 당신과 어떠한 연결도 없으며, 저희나 Google의 광고에 쓰이지도 않습니다.", "ready": "Juxt 사용 준비 완료", - "ready_text": "처음으로, 몇몇 커뮤니티를 확인하여, 사람들이 무엇을 게시하고 있는지 보세요. 이번 기회에 Juxt 내 사람들을 숙지하세요. 새로운 발견을 할 수도 있어요!", + "ready_text": "먼저 유명한 커뮤니티부터 찾아보세요! 사람들이 뭘 올리는지 확인하고, 그리고 이번 기회에 사람들이 어떤 걸 하는지 찾아보세요. 새로운 발견이 될 수도 있으니까요!", "done": "Juxt에서 좋은 시간을 보내시기 바랍니다!", "done_button": "가자!", "guest": "게스트 모드", - "guest_text": "현재로서는 당신의 계정을 조회할 수 없으며, 아마 지금 Nintendo Network 계정으로 로그인하고 있다는 것입니다. Juxt를 둘러볼 수는 있지만, Pretendo Network 계정으로 로그인할 때까지 “맞네!“를 달거나 자신의 포스트를 게시할 수는 없습니다. 자세한 정보는 Discord 서버에서 확인하실 수 있습니다.", + "guest_text": "어라? 계정을 확인할 수 없네요. 닌텐도 네트워크로 로그인을 하신 걸 수도 있어요. Juxt를 둘러보실 수는 있지만, '당근이지!'를 게시물에 달거나 게시물을 만드는 것은 안되지만, Pretendo 네트워크로 로그인하면 Juxt의 모든 기능을 사용하실 수 있어요. 더 많은 정보는 디스코드 서버를 확인해주세요.", "guest_button": "알겠어요" } } diff --git a/apps/juxtaposition-ui/src/translations/lt.json b/apps/juxtaposition-ui/src/translations/lt.json index 287e1c0d..911f8cf6 100644 --- a/apps/juxtaposition-ui/src/translations/lt.json +++ b/apps/juxtaposition-ui/src/translations/lt.json @@ -4,7 +4,7 @@ "notifications": "Pranešimai", "communities": "Bendruomenės", "messages": "Žinutės", - "go_back": "Sugryšti", + "go_back": "Grįžti", "exit": "Išeiti", "user_page": "Vartotojo puslapis", "more": "Užkrauti daugiau įkelimų", @@ -12,7 +12,7 @@ "close": "Uždaryti", "save": "Išsaugoti", "activity_feed": "Įrašų puslapis", - "private": "Privati", + "private": "Privatus", "yeahs": "Patiktukai", "next": "Toliau" }, @@ -35,7 +35,7 @@ "popular": "Populiarus įkelimai", "new": "Naujas įkelimas", "recent": "Naujausi įkelimai", - "verified": "Verifikuoti įkelimai" + "verified": "Patvirtinti įkėlimai" }, "user_page": { "country": "Šalis", @@ -83,9 +83,9 @@ "welcome": "Sveiki atvykę į Juxtaposition!", "beta": "Įspėjimas dėl Beta versijos", "beta_text": { - "first": "Tu tuoj išbandysi pirmają Juxt beta versiją. Tai reiškia kad, dalykai gali pasikeisti betkuriuo metu kai tu naudojant Juxt.", - "second": "Tai gali apimti visišką databazės išvalymą betos pabaigoje arba perijodo metu.", - "third": "Svetainė, jos programinis turinys, ir visas čia rastas turinys yra paduota \"kaip yra\" ir \"kaip pasiekiama\".Pretendo tinklas nesuteikia jokių tiesioginių ar numanomų garantijų dėl svetainės, jos programinės įrangos ar bet kokio turinio tinkamumo ar tinkamumo naudoti." + "first": "Tu tuoj išbandysi pirmają Juxt beta versiją. Tai reiškia kad, dalykai gali pasikeisti betkuriuo metu.", + "second": "Tai gali apimti visišką duomenų bazės išvalymą Betos stafijos pabaigoje arba periodo metu.", + "third": "Svetainė, jos programinis turinys, ir visas čia rastas turinys yra paduota \"kaip yra\" ir \"kaip pasiekiama\". Pretendo tinklas nesuteikia jokių tiesioginių ar numanomų garantijų dėl svetainės, jos programinės įrangos ar bet kokio turinio tinkamumo ar tinkamumo naudoti." }, "info": "Kas yra Juxtaposition?", "rules_text": { diff --git a/apps/juxtaposition-ui/src/translations/lv.json b/apps/juxtaposition-ui/src/translations/lv.json new file mode 100644 index 00000000..df3b1cf9 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/lv.json @@ -0,0 +1,126 @@ +{ + "global": { + "activity_feed": "Aktivitātes saraksts", + "messages": "Ziņas", + "notifications": "Paziņojumi", + "go_back": "Doties Atpakaļ", + "back": "Atpakaļ", + "no_posts": "Nav ziņojumu", + "private": "Privāts", + "close": "Aizvērt", + "exit": "Iziet", + "more": "Ielādēt vairāk ziņojumus", + "save": "Saglabāt", + "next": "Nākamais", + "yeahs": "Reakcijas", + "communities": "Kopienas", + "user_page": "Lietotāju Lapa" + }, + "all_communities": { + "announcements": "Paziņojumi", + "ann_string": "Spied šeit, lai skatītu nesen sūtītos ziņojumus!", + "new_communities": "Jaunas kopienas", + "show_all": "Rādīt visus", + "text": "Visas kopienas", + "search": "Meklēt kopienas...", + "popular_places": "Bieži apmeklētas vietas" + }, + "community": { + "follow": "Sekot kopienai", + "following": "Seko līdzi", + "followers": "Sekotāji", + "posts": "Ziņojumi", + "tags": "Birkas", + "recent": "Nesen sūtītie ziņojumi", + "new": "Jauna ziņa", + "verified": "Apstiprinātās ziņas", + "popular": "Bieži skatītie ziņojumi" + }, + "user_page": { + "birthday": "Dzimšanas diena", + "posts": "Ziņojumi", + "friends": "Draugi", + "following": "Seko", + "follow_user": "Sekot", + "following_user": "Seko", + "befriend": "Pievienot draugu sarakstam", + "pending": "Gaidošie", + "unfriend": "Noņemt no draugu saraksta", + "no_following": "Nevienam neseko", + "country": "Valsts", + "followers": "Sekotāji", + "no_friends": "Nav neviena drauga", + "no_followers": "Nav neviena sekotāja", + "game_experience": "Spēles spēlēšanas pieredze" + }, + "user_settings": { + "profile_settings": "Profila iestatījumi", + "show_country": "Rādīt valsti profilā", + "show_birthday": "Rādīt dzimšanas dienu profilā", + "show_comment": "Radīt komentārus pie profila", + "profile_comment": "Profila komentāri", + "swearing": "Profila komentārs nedrīkst saturēt necenzētu valodu.", + "show_game": "Rādīt spēļu pieredzi profilā" + }, + "activity_feed": { + "empty": "Te ir tukšs. Pamēģini kādam sekot!" + }, + "notifications": { + "none": "Nav ziņojumi.", + "new_follower": "sāka tev sekot!" + }, + "new_post": { + "post_to": "Nosūtīt uz", + "new_post_text": "Jauns ziņojums", + "text_hint": "Spied šeit, lai rakstītu ziņojumu.", + "swearing": "Ziņojums nedrīkst saturēt necenzētu valodu." + }, + "setup": { + "welcome": "Laipni lūgts Juxtaposition!", + "beta": "Beta brīdinājums", + "info": "Kas ir Juxtaposition?", + "rules": "Juxt noteikumi", + "rules_text": { + "second": "Ziņas var tikt skatītas visā pasaulē", + "fourth": "Esi jauks pret citiem", + "sixth": "Neraksti neviena cilvēka personīgo informāciju", + "eighth": "Neraksti spoilerus", + "tenth": "Uzvedības kodeksa pārkāpumi", + "first": "Turpmāk minētās ir svarīgas vadlīnijas, kas ir ieviestas ar mērķi padarīt Juxt par aizraujošu un baudāmu pieredzi visiem. Juxt uzvedības kodekss satur detalizētu informāciju, tādēļ lūdzu izlasiet to uzmanīgi.", + "third": "Juxt satur daudzas spēlētāju kopienas, kurās cilvēki no visas pasaules var dalīties ar savām domām. Kad tu raksti kopienai, atceries, ka visi var to redzēt, tādēļ, lūdzu, izpaud sevi veidā, kas visiem ir patīkams. Izmanto vispārējo saprātu, un padomā pirms raksti. Juxt ir serviss, kas ir pieejams caur internetu, tādēļ paturi prātā, ka arī cilvēki, kas neizmanto Juxt var redzēt tavas ziņas. Papildus tam, visi komentāri, ko tu raksti pie savu draugu ziņām būs redzami arī visiem citiem. Lūdzu atceries šo.", + "fifth": "Lai paturētu Juxt par foršu vietu visiem, mēs prasām, lai tu esi saudzīgs pret citiem lietotājiem. Palīdzi mums saglabāt Juxt patīkamu, nerakstot neko neatbilstošu vai aizvainošu.", + "seventh": "Atcerieties, ka zināt kādu Juxt nav tas pats, kas viņu personīgi pazīt reālajā dzīvē. Nekad nevienam neraksti savu e-pasta adresi, dzīvesvietas adresi, darba vai skolas nosaukumu vai citu personisku informāciju caur Juxt, un nekad nedalieties arī ar citu personu personīgo informāciju. Turklāt, ja kāds, ar ko esat iepazinies Juxt, aicina tevi tikties reālajā pasaulē, nepieņem to. Juxt ir tiešsaistes kopiena, un to nevajadzētu izmantot, lai organizētu reālas tikšanās.", + "ninth": "Daži cilvēki ierodas Juxt meklējot padomus un trikus priekš spēlēm, bet citi grib paši atklāt spēles noslēpumus. Ziņas, kas atklāj šos noslēpumus par spēli vai tās scenāriju sauc par \"spoileriem\". Ja tu raksti kaut ko, kas varētu būt spoileris, pārliecinies, ka atzīmējiet spoilera izvēles rūtiņu. Tādā veidā, cilvēki, kas nevēlas tikt spoiloti neredzēs šo ziņu.", + "eleventh": "Mūsu mērķis ir saglabāt Juxt par draudzīgu un patīkamu visiem. Ja kāds pārkāps Juxt rīcības kodeksu, mēs veiksim atbilstošus pasākumus, tostarp bloķēsim pārkāpēju lietotāju un/vai konsoli.", + "twelfth": "Vai tu esi spēlējis šo spēli?", + "thirteenth": "Ja tu raksti kopienā par spēli, kuru tu esi spēlējis, tavām ziņām būs ikona, kas norāda, ka tu esi to spēlēji." + }, + "experience": "Spēļu spēlēšanas pieredze", + "experience_text": { + "beginner": "Iesācējs", + "info": "Lūdzu pastāsti mums, kā tu aprakstītu savu pieredzi ar videospēlēm. Tu vari vēlāk mainīt šo iestatījumu.", + "intermediate": "Piepratējs", + "expert": "Eksperts" + }, + "ready": "Gatavs sākt izmantot Juxt", + "ready_text": "Vispirms apskati dažas kopienas un noskaidro par ko citi cilvēki raksta. Izmanto šo iespēju, lai iepazīstinātu sevi ar Juxt. Tu pa ceļam varētu veikt jaunus atklājumus!", + "done": "Priecājies izmantojot Juxt servisu!", + "done_button": "Aiziet!", + "guest": "Viesa režīms", + "guest_text": "Mēs pašlaik nevaram apstiprināt tavu kontu, kas visticamāk nozīmē, ka tu pašlaik esi ielogojies ar Nintendo Network kontu. Tu varēsi apskatīt Juxt, bet nevarēsi reaģēt uz ziņām vai rakstīt tās pats, līdz ielogosies ar Pretendo Network kontu. Lai iegūtu vairāk informāciju apskati mūsu Discord serveri.", + "guest_button": "Es saprotu", + "beta_text": { + "first": "Tu izmēģināsi pirmo Juxt publisko beta versiju. Tas nozīmē, ka daudz lietas vēl nav pabeigtas, un daudz, kas vēl tiks mainīts.", + "third": "Mājaslapa, tās programmatūra un viss tajā atrodamais saturs tiek nodrošināts pēc \"tā, kā ir\" un \"kā pieejams\" principa. Pretendo Network nesniedz nekādas garantijas - ne tiešas, ne netiešas - par tīmekļa vietnes, tās programmatūras vai jebkāda tās satura piemērotību vai lietojamību.", + "second": "Tas var saturēt absolūtu datubāžu izdzēšanu beta testēšanas perioda laikā vai pēc tā." + }, + "info_text": "Juxt ir lietotāju vadīta spēlētāju kopiena, kurā tu vari mijiedarboties ar cilvēkiem\n no visas pasaules. Tu vari rakstīt vai uzzīmēt ziņas spēļu kopienām vai arī sūtīt ziņas tieši draugiem.", + "google": "Google analītika", + "google_text": "Juxtaposition izmanto \"Google Analytics\", lai izsekotu, kā lietotāji izmanto mūsu pakalpojumus. Savāktie dati ietver, bet neaprobežojas ar apmeklējuma laiku, apmeklētajām lapām un vietnē pavadīto laiku. Šie dati nav nekādā veidā saistīti ar jums(?), un mēs vai Google(?) tos neizmanto reklāmām.", + "welcome_text": "Juxt ir spēlētāju kopiena, kas savieno cilvēkus no visas pasaules, izmantojot Mii varoņus. Izmanto Juxt, lai dalītos savā spēlēšanas pieredzē un satiktu cilvēkus no visas pasaules." + }, + "language": "Latviešu valoda", + "messages": { + "coming_soon": "Ziņas vēl nav gatavas. Apskati atkal pēc kāda laika!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/nl.json b/apps/juxtaposition-ui/src/translations/nl.json index 1f40e945..7e7aab43 100644 --- a/apps/juxtaposition-ui/src/translations/nl.json +++ b/apps/juxtaposition-ui/src/translations/nl.json @@ -2,13 +2,13 @@ "language": "Nederlands", "global": { "user_page": "Gebruikerspagina", - "activity_feed": "Activiteitenfeed", - "communities": "Gemeenschappen", + "activity_feed": "Overzicht", + "communities": "Community's", "messages": "Berichten", - "notifications": "Notificaties", + "notifications": "Meldingen", "go_back": "Ga Terug", "back": "Terug", - "yeahs": "Ja", + "yeahs": "Ja's", "more": "Laad Meer Posts", "no_posts": "Geen Posts", "private": "Privé", @@ -18,16 +18,16 @@ "next": "Volgende" }, "all_communities": { - "text": "Alle Gemeenschappen", + "text": "Alle Community's", "announcements": "Aankondigingen", "ann_string": "Klik hier om recente aankondigen te bekijken!", - "popular_places": "Populaire Gemeenschappen", - "new_communities": "Nieuwe Gemeenschappen", + "popular_places": "Populaire Community's", + "new_communities": "Nieuwe Community's", "show_all": "Alles weergeven", - "search": "Zoek gemeenschappen..." + "search": "Zoek community's..." }, "community": { - "follow": "Volg gemeenschap", + "follow": "Volg Community", "following": "Volgend", "followers": "Volgers", "posts": "Posts", @@ -39,35 +39,35 @@ }, "user_page": { "country": "Land", - "birthday": "Geboortedatum", - "game_experience": "Game Ervaring", + "birthday": "Verjaardag", + "game_experience": "Game-ervaring", "posts": "Posts", "friends": "Vrienden", - "following": "Volgend", + "following": "Gevolgde gebruikers", "followers": "Volgers", - "follow_user": "Volg", + "follow_user": "Volgen", "following_user": "Volgend", "befriend": "Bevriend", "pending": "Wachtend", "unfriend": "Ontvriend", "no_friends": "Geen vrienden", - "no_following": "Volgt niemand", - "no_followers": "Geen volgers" + "no_following": "Volgt Niemand", + "no_followers": "Geen Volgers" }, "user_settings": { - "profile_settings": "Profiel instellingen", - "show_country": "Weergeef Land op Profiel", - "show_birthday": "Weergeef Geboortedatum op Profiel", - "show_game": "Weergeef Game Ervaring op Profiel", - "show_comment": "Weergeef Omschrijving op Profiel", - "profile_comment": "Profiel Omschrijving", - "swearing": "Profiel omschrijving kan geen expliciete tekst bevatten." + "profile_settings": "Profielinstellingen", + "show_country": "Geef Land op Profiel weer", + "show_birthday": "Geef Verjaardag op Profiel weer", + "show_game": "Geef Game-ervaring op Profiel weer", + "show_comment": "Geef Omschrijving op Profiel weer", + "profile_comment": "Profielomschrijving", + "swearing": "Profielomschrijving kan geen expliciete tekst bevatten." }, "activity_feed": { "empty": "Het is eenzaam hier. Probeer iemand te volgen!" }, "notifications": { - "none": "Geen notificaties.", + "none": "Geen meldingen.", "new_follower": "volgt jou nu!" }, "new_post": { @@ -81,12 +81,12 @@ }, "setup": { "welcome": "Welkom op Juxtaposition!", - "welcome_text": "Juxt is een gaming community welk mensen van over de hele wereld verbind doormiddel van Mii karakters. Gebruik Juxt om jouw ervaringen tijdens het gamen te delen en mensen te ontmoeten over de hele wereld.", + "welcome_text": "Juxt is een gaming community welk mensen van over de hele wereld verbind door middel van Mii karakters. Gebruik Juxt om jouw ervaringen tijdens het gamen te delen en mensen te ontmoeten over de hele wereld.", "beta": "Beta-disclaimer", "beta_text": { "first": "Je staat op het punt de eerste publieke beta van Juxt te gebruiken. Dit betekent dat veel onzeker is en ook van alles kan veranderen.", - "second": "Dit kan mogelijk een volledige reset van de database bevatten aan het einde van de beta periode of tijdens deze periode.", - "third": "De website, de software die het bevat, alsook alle content dat te vinden is op deze website, worden geleverd op een \"as is\" en \"as available\" basis. Pretendo Network geeft geen garantstellingen, ofwel expliciet ofwel impliciet, met betrekking tot de geschiktheid of bruikbaarheid van de website, de software die het bevat of de content op de website." + "second": "Dit kan mogelijk een volledige reset van de database aan het einde van of tijdens de beta periode omvatten.", + "third": "De website, de software die het bevat, alsook alle content die te vinden is op deze website, worden geleverd op een \"as is\" en \"as available\" basis. Pretendo Network geeft geen garantstellingen, noch expliciet noch impliciet, met betrekking tot de geschiktheid of bruikbaarheid van de website, de software die het bevat of de content op de website." }, "info": "Wat is Juxtaposition?", "info_text": "Juxt is een gebruikers-gedreven gaming community waar je kan communiceren met mensen\n over de hele wereld. Je kan posts schrijven of tekenen in game communities of berichten rechtstreeks naar je vrienden sturen.", @@ -94,19 +94,19 @@ "rules_text": { "first": "De volgende tekst bevat belangrijke richtlijnen om Juxt een leuke en gezellige omgeving voor iedereen te maken. De Juxt gedragscode bevat belangrijke gedetailleerde informatie, dus lees het aandachtig door.", "second": "Posts Kunnen Overal Ter Wereld Bekeken Worden", - "third": "Juxt bevat vele gaming communities waar mensen over de hele wereld hun gedachten kunnen delen. Als je in een community post, onthoud dan dat iedereen dit kan zien, dus uit je alsjeblieft op een manier die voor iedereen prettig is. Gebruik je gezonde verstand en denk eerst na voor je iets post. Juxt is daarnaast ook een dienst welk bereikbaar is via het internet, dus houd rekening met het feit dat mensen die Juxt niet gebruiken ook je posts mogelijk kunnen zien. Daarnaast zijn reacties die je maakt op posts van je vrienden niet alleen zichtbaar voor je vrienden, maar ook voor mensen over de hele wereld. Houd hier rekening mee.", + "third": "Juxt bevat vele gaming community's waar mensen over de hele wereld hun gedachten kunnen delen. Als je in een community post, onthoud dan dat iedereen dit kan zien, dus uit je alsjeblieft op een manier die voor iedereen prettig is. Gebruik je gezonde verstand en denk eerst na voor je iets post. Juxt is daarnaast ook een dienst die bereikbaar is via het internet, dus houd rekening met het feit dat mensen die Juxt niet gebruiken ook je posts mogelijk kunnen zien. Daarnaast zijn reacties die je maakt op posts van je vrienden niet alleen zichtbaar voor je vrienden, maar ook voor mensen over de hele wereld. Houd hier rekening mee.", "fourth": "Wees Aardig Tegen Elkaar", "fifth": "Om Juxt een leuke omgeving voor iedereen te houden vragen wij van jou om rekening te houden met anderen. Help ons om Juxt zo leuk mogelijk te houden door niets ongepasts of beledigend te posten.", - "sixth": "Post Geen Persoonlijke Gegevens—Die van Jezelf of Van Anderen", - "seventh": "Onthoud, iemand in Juxt kennen is niet hetzelfde als iemand in het echt kennen. Deel nooit je e-mail adres, huisadres, de naam van je school of werk of wat voor een persoonlijke identificeerbare informatie dan ook met een ander en deel ook geen van deze gegevens van iemand anders. Daarnaast, als iemand die je ontmoet hebt op Juxt je vraagt om diegene in het echt te ontmoeten, accepteer dit niet. Juxt is een online community en moet niet gebruikt worden om ontmoetingen in het echt te faciliteren.", + "sixth": "Post Geen Persoonlijke Gegevens—Die van Jezelf of van Anderen", + "seventh": "Onthoud, iemand in Juxt kennen is niet hetzelfde als iemand in het echt kennen. Deel nooit je e-mail adres, huisadres, de naam van je school of werk of wat voor een persoonlijke identificeerbare informatie dan ook met een ander en deel zulke gegevens van iemand anders niet. Daarnaast, als iemand die je ontmoet hebt op Juxt je vraagt om diegene in het echt te ontmoeten, accepteer dit niet. Juxt is een online community en moet niet gebruikt worden om ontmoetingen in het echt te faciliteren.", "eighth": "Post Geen Spoilers", - "ninth": "Engels\nSommige mensen komen naar Juxt voor tips en trucs voor games, anderen willen zelf de geheimen van een game ontdekken. Berichten die geheimen van een game of het verhaal ervan onthullen, worden 'spoilers' genoemd. Als je iets post over een game dat een spoiler zou kunnen zijn, zorg er dan voor dat je het vakje Spoilers aanvinkt voordat je je post verstuurt. Zo zien mensen die niet verwend willen worden je bericht niet.", + "ninth": "Sommige mensen komen naar Juxt voor tips en trucs voor games, anderen willen zelf de geheimen van een game ontdekken. Berichten die geheimen van een game of het verhaal ervan onthullen, worden 'spoilers' genoemd. Als je iets post over een game dat een spoiler zou kunnen zijn, zorg er dan voor dat je het vakje Spoilers aanvinkt voordat je je post verstuurt. Zo zien mensen die geen spoilers willen zien je bericht niet.", "tenth": "Gedragscode overtredingen", "eleventh": "Ons doel is om Juxt leuk en gezellig voor iedereen te houden. Mocht iemand de Juxt Gedragscode overtreden, dan zullen wij gepaste maatregelen nemen. Deze maatregelen omvatten onder andere het blokkeren van toegang voor het account of de console van de gebruiker die de gedragscode overtreedt.", "twelfth": "Heb Je Dat Spel Gespeeld?", "thirteenth": "Als je post in een community voor een game welk je hebt gespeeld, bevat je post een icoontje dat aangeeft dat je deze game hebt gespeeld." }, - "experience": "Game Ervaring", + "experience": "Game-ervaring", "experience_text": { "info": "Vertel ons alstublieft hoe u uw ervaringsniveau met games zou omschrijven. U kunt deze instelling later wijzigen.", "beginner": "Beginner", @@ -116,11 +116,11 @@ "google": "Google Analytics", "google_text": "Juxtaposition gebruikt Google Analytics om bij te houden hoe gebruikers onze dienst gebruiken. De data die we verzamelen bevat onder andere tijd van bezoek, bezochte pagina's en hoe lang het bezoek duurde. Deze data is op geen enkele manier gekoppeld aan jou en wordt niet gebruikt door Google of ons om advertenties aan jou te tonen.", "ready": "Klaar om Juxt te Gebruiken", - "ready_text": "Bekijk allereerst wat communities en zie waar mensen van over de hele wereld over posten. Gebruik deze kans om je bekend te maken met Juxt. Mogelijk ontdek je iets nieuws op deze manier!", + "ready_text": "Bekijk eerst wat community's en zie waar mensen van over de hele wereld over posten. Gebruik deze kans om je bekend te maken met Juxt. Misschien ontdek je iets nieuws op deze manier!", "done": "Veel plezier in Juxt!", "done_button": "Laten We Beginnen!", - "guest": "Gast Modus", - "guest_text": "We kunnen momenteel jouw account niet verifiëren, dit betekent waarschijnlijk dat je probeert in te loggen met een Nintendo Network account. Je kan nog steeds Juxt bezoeken, maar je kan geen Yeah!s geven op posts, daarnaast kan je geen posts maken totdat je inlogt met een Pretendo Network account. Bezoek de Discord server voor meer informatie.", + "guest": "Gastmodus", + "guest_text": "We kunnen momenteel jouw account niet verifiëren, dit betekent waarschijnlijk dat je probeert in te loggen met een Nintendo Network account. Je kan nog steeds Juxt bezoeken, maar je kan posts geen Ja! geven. Daarnaast kan je geen posts maken totdat je inlogt met een Pretendo Network account. Bezoek de Discord server voor meer informatie.", "guest_button": "Begrepen" } } diff --git a/apps/juxtaposition-ui/src/translations/pt.json b/apps/juxtaposition-ui/src/translations/pt.json index 91b5367a..873573dc 100644 --- a/apps/juxtaposition-ui/src/translations/pt.json +++ b/apps/juxtaposition-ui/src/translations/pt.json @@ -1,126 +1,126 @@ { - "language": "Português", - "global": { - "user_page": "Perfil do Utilizador", - "activity_feed": "Atividade", - "communities": "Comunidades", - "messages": "Mensagens", - "notifications": "Notificações", - "go_back": "Voltar", - "back": "Anterior", - "yeahs": "Fixes", - "more": "Carregar mais publicações", - "no_posts": "Sem publicações", - "private": "Privado", - "close": "Fechar", - "save": "Guardar", - "exit": "Sair", - "next": "Seguinte" - }, - "all_communities": { - "text": "Todas as comunidades", - "announcements": "Anúncios", - "ann_string": "Clica aqui para veres os anúncios recentes!", - "popular_places": "Lugares Populares", - "new_communities": "Novas Comunidades", - "show_all": "Mostrar todas", - "search": "Procurar comunidades..." - }, - "community": { - "follow": "Seguir Comunidade", - "following": "A seguir", - "followers": "Seguidores", - "posts": "Publicações", - "tags": "Tags", - "recent": "Publicações Recentes", - "popular": "Publicações Populares", - "verified": "Publicações Verificadas", - "new": "Nova Publicação" - }, - "user_page": { - "country": "País", - "birthday": "Aniversário", - "game_experience": "Experiência de Jogo", - "posts": "Publicações", - "friends": "Amigos", - "following": "A seguir", - "followers": "Seguidores", - "follow_user": "Seguir", - "following_user": "Seguidores", - "befriend": "Tornar-se Amigo", - "pending": "Pendente", - "unfriend": "Remover Amizade", - "no_friends": "Sem Amigos", - "no_following": "Não segue ninguém", - "no_followers": "Sem seguidores" - }, - "user_settings": { - "profile_settings": "Preferências de Perfil", - "show_country": "Mostrar país no perfil", - "show_birthday": "Mostrar data de aniversário no perfil", - "show_game": "Mostrar experiência de jogo no perfil", - "show_comment": "Mostrar comentário no perfil", - "profile_comment": "Comentário do Perfil", - "swearing": "O comentário do perfil não pode ter linguagem obscena." - }, - "activity_feed": { - "empty": "Isto está muito vazio. Começa a seguir alguém!" - }, - "notifications": { - "none": "Sem notificações.", - "new_follower": "seguiu-te!" - }, - "new_post": { - "new_post_text": "Nova publicação", - "post_to": "Publicar em", - "text_hint": "Toca aqui para criar uma publicação.", - "swearing": "A publicação não pode ter linguagem obscena." - }, - "messages": { - "coming_soon": "A funcionalidade de mensagens ainda não está disponível. Volta em breve!" - }, - "setup": { - "welcome": "Bem-vindo a Juxtaposition!", - "welcome_text": "Juxt é uma comunidade de jogadores que liga pessoas de todo o mundo utilizando personagens Mii. Usa Juxt para partilhar as tuas experiências de jogo e conhecer novas pessoas de todo o mundo.", - "beta": "Nota sobre Beta", - "beta_text": { - "first": "Estás prestes a experimentar a primeira versão beta pública de Juxt. Isto significa que ainda há muito a fazer e que muitas coisas ainda poderão mudar a qualquer momento.", - "second": "No final do período beta, os dados registados na plataforma poderão vir a ser apagados.", - "third": "Este website, o seu software e todos os conteúdos nele conteúdos são disponibilizados “tal como estão” e “quando disponíveis”. A Pretendo Network não oferece quaisquer garantias, quer expressas quer implícitas, a respeito da integridade ou disponibilidade do website, do seu software ou dos seus conteúdos." + "language": "Português", + "global": { + "user_page": "Perfil do Utilizador", + "activity_feed": "Atividade", + "communities": "Comunidades", + "messages": "Mensagens", + "notifications": "Notificações", + "go_back": "Voltar", + "back": "Anterior", + "yeahs": "Fixes", + "more": "Carregar mais publicações", + "no_posts": "Sem publicações", + "private": "Privado", + "close": "Fechar", + "save": "Guardar", + "exit": "Sair", + "next": "Seguinte" }, - "info": "O que é Juxtaposition?", - "info_text": "Juxt é uma comunidade de utilizadores que liga pessoas\n de todo o mundo. Podes escrever ou desenhar publicações nas comunidades dos jogos ou enviar mensagens diretamente aos teus amigos.", - "rules": "Regras de Juxt", - "rules_text": { - "first": "Estas são algumas regras que permitem que Juxt seja uma experiência divertida para todos. O código de conduta Juxt contém informação detalhada, por isso lê-o com atenção.", - "second": "Publicações podem ser vistas em todo o mundo", - "third": "Juxt contém várias comunidades de jogadores onde pessoas de todo o mundo podem partilhar as suas ideias. Quando publicas numa comunidade, lembra-te que todos podem ver as tuas publicações, por isso expressa-te de uma maneira que possa ser divertida para todos. Usa o senso comum e pensa antes de publicar. Juxt é um serviço que também está acessível pela Internet, por isso pessoas que não usam Juxt também podem ver as tuas publicações. Adicionalmente, quaisquer comentários que faças em publicações de amigos são visíveis não só por estes, mas também por pessoas de todo o mundo. Tem isto em consideração.", - "fourth": "Sê simpático com os outros", - "fifth": "De forma a mantermos Juxt um lugar divertido para todos, pedimos-te que penses nas outras pessoas. Ajuda-nos a manter Juxt uma boa experiência ao não fazer publicações impróprias ou ofensivas.", - "sixth": "Não partilhes informações pessoas tuas nem de ninguém", - "seventh": "Lembra-te, conhecer alguém em Juxt não é o mesmo que conhecer alguém na vida real. Nunca partilhes com ninguém em Juxt o teu endereço de e-mail, morada, trabalho, nome da escola ou qualquer outra informação que te possa identificar nem partilhes informação de outras pessoas. Adicionalmente, se alguém que conheceres em Juxt te convidar para que se conheçam no mundo real, não aceites. Juxt é uma comunidade online e não deve ser usada para marcar encontros presenciais.", - "eighth": "Não publiques Spoilers", - "ninth": "Algumas pessoas vêm a Juxt à procura de dicas e truques para jogos, mas outras preferem descobrir os jogos por si mesmas. Publicações que revelam segredos de um jogo ou a sua história são chaamdas de \"spoilers.\" Se vais publicar algo sobre um jogo que pode ser um spoiler, marca a respetiva caixa antes de enviares a tua publicação. Desta forma, pessoas que não querem ver spoilers não verão a tua publicação.", - "tenth": "Violações do código de conduta", - "eleventh": "O nosso objetivo é fazer de Juxt uma comunidade divertida para todos. No caso de alguém violar o Código de Conduta de Juxt, iremos tomar as medidas necessárias que poderão incluir banir o utilizador ou a consola.", - "twelfth": "Já jogaste este jogo?", - "thirteenth": "Se publicares numa comunidade de um jogo que já jogaste, as tuas publicações irão incluir um íncone que indica que já o jogaste." + "all_communities": { + "text": "Todas as comunidades", + "announcements": "Anúncios", + "ann_string": "Clica aqui para veres os anúncios recentes!", + "popular_places": "Lugares Populares", + "new_communities": "Novas Comunidades", + "show_all": "Mostrar todas", + "search": "Procurar comunidades..." }, - "experience": "Experiência de jogo", - "experience_text": { - "info": "Fala-nos um pouco da tua experiência com jogos. Podes alterar estas definições mais tarde.", - "beginner": "Iniciante", - "intermediate": "Intermédio", - "expert": "Avançado" + "community": { + "follow": "Seguir Comunidade", + "following": "A seguir", + "followers": "Seguidores", + "posts": "Publicações", + "tags": "Tags", + "recent": "Publicações Recentes", + "popular": "Publicações Populares", + "verified": "Publicações Verificadas", + "new": "Nova Publicação" }, - "google": "Google Analytics", - "google_text": "Juxtaposition usa Google Analytics de forma a recolher informações de como os utilizadores usam o serviço. Os dados recolhidos incluem, mas não estão limitados a, hora da visita, páginas visitadas e tempo passado no site. Estes dados não estão associados a ti de maneira nenhuma e não são usados para anúncios por nós nem pela Google.", - "ready": "Pronto para usar Juxt", - "ready_text": "Começa por explorar algumas comunidades e ver o que as pessoas de todo o mundo estão a publicar. Aproveita esta oportunidade para te familiarizares com Juxt. Pode ser que descubras algo novo pelo caminho!", - "done": "Diverte-te com Juxt!", - "done_button": "Vamos a isso!", - "guest": "Modo de convidado", - "guest_text": "Não nos foi possível verificar a tua conta de momento, o que provavelmente significa que tens sessão iniciada com uma conta Nintendo Network. Podes explorar Juxt, mas não te será possível marcar publicações com Fixe! ou fazer publicações até que inicies sessão com uma conta Pretendo Network. Para mais informações visita o nosso servidor Discord.", - "guest_button": "Compreendo" - } + "user_page": { + "country": "País", + "birthday": "Aniversário", + "game_experience": "Experiência de Jogo", + "posts": "Publicações", + "friends": "Amigos", + "following": "A seguir", + "followers": "Seguidores", + "follow_user": "Seguir", + "following_user": "Seguidores", + "befriend": "Tornar-se Amigo", + "pending": "Pendente", + "unfriend": "Remover Amizade", + "no_friends": "Sem Amigos", + "no_following": "Não segue ninguém", + "no_followers": "Sem seguidores" + }, + "user_settings": { + "profile_settings": "Preferências de Perfil", + "show_country": "Mostrar país no perfil", + "show_birthday": "Mostrar data de aniversário no perfil", + "show_game": "Mostrar experiência de jogo no perfil", + "show_comment": "Mostrar comentário no perfil", + "profile_comment": "Comentário do Perfil", + "swearing": "O comentário do perfil não pode ter linguagem obscena." + }, + "activity_feed": { + "empty": "Isto está muito vazio. Começa a seguir alguém!" + }, + "notifications": { + "none": "Sem notificações.", + "new_follower": "seguiu-te!" + }, + "new_post": { + "new_post_text": "Nova publicação", + "post_to": "Publicar em", + "text_hint": "Toca aqui para criar uma publicação.", + "swearing": "A publicação não pode ter linguagem obscena." + }, + "messages": { + "coming_soon": "A funcionalidade de mensagens ainda não está disponível. Volta em breve!" + }, + "setup": { + "welcome": "Bem-vindo a Juxtaposition!", + "welcome_text": "Juxt é uma comunidade de jogadores que liga pessoas de todo o mundo utilizando personagens Mii. Usa Juxt para partilhar as tuas experiências de jogo e conhecer novas pessoas de todo o mundo.", + "beta": "Nota sobre Beta", + "beta_text": { + "first": "Estás prestes a experimentar a primeira versão beta pública de Juxt. Isto significa que ainda há muito a fazer e que muitas coisas ainda poderão mudar a qualquer momento.", + "second": "No final do período beta, os dados registados na plataforma poderão vir a ser apagados.", + "third": "Este website, o seu software e todos os conteúdos nele conteúdos são disponibilizados “tal como estão” e “quando disponíveis”. A Pretendo Network não oferece quaisquer garantias, quer expressas quer implícitas, a respeito da integridade ou disponibilidade do website, do seu software ou dos seus conteúdos." + }, + "info": "O que é Juxtaposition?", + "info_text": "Juxt é uma comunidade de utilizadores que liga pessoas\n de todo o mundo. Podes escrever ou desenhar publicações nas comunidades dos jogos ou enviar mensagens diretamente aos teus amigos.", + "rules": "Regras de Juxt", + "rules_text": { + "first": "Estas são algumas regras que permitem que Juxt seja uma experiência divertida para todos. O código de conduta Juxt contém informação detalhada, por isso lê-o com atenção.", + "second": "Publicações podem ser vistas em todo o mundo", + "third": "Juxt contém várias comunidades de jogadores onde pessoas de todo o mundo podem partilhar as suas ideias. Quando publicas numa comunidade, lembra-te que todos podem ver as tuas publicações, por isso expressa-te de uma maneira que possa ser divertida para todos. Usa o senso comum e pensa antes de publicar. Juxt é um serviço que também está acessível pela Internet, por isso pessoas que não usam Juxt também podem ver as tuas publicações. Adicionalmente, quaisquer comentários que faças em publicações de amigos são visíveis não só por estes, mas também por pessoas de todo o mundo. Tem isto em consideração.", + "fourth": "Sê simpático com os outros", + "fifth": "De forma a mantermos Juxt um lugar divertido para todos, pedimos-te que penses nas outras pessoas. Ajuda-nos a manter Juxt uma boa experiência ao não fazer publicações impróprias ou ofensivas.", + "sixth": "Não partilhes informações pessoais", + "seventh": "Lembra-te, conhecer alguém em Juxt não é o mesmo que conhecer alguém na vida real. Nunca partilhes com ninguém em Juxt o teu endereço de e-mail, morada, trabalho, nome da escola ou qualquer outra informação que te possa identificar nem partilhes informação de outras pessoas. Adicionalmente, se alguém que conheceres em Juxt te convidar para que se conheçam no mundo real, não aceites. Juxt é uma comunidade online e não deve ser usada para marcar encontros presenciais.", + "eighth": "Não publiques Spoilers", + "ninth": "Algumas pessoas vêm a Juxt à procura de dicas e truques para jogos, mas outras preferem descobrir os jogos por si mesmas. Publicações que revelam segredos de um jogo ou a sua história são chaamdas de \"spoilers.\" Se vais publicar algo sobre um jogo que pode ser um spoiler, marca a respetiva caixa antes de enviares a tua publicação. Desta forma, pessoas que não querem ver spoilers não verão a tua publicação.", + "tenth": "Violações do código de conduta", + "eleventh": "O nosso objetivo é fazer de Juxt uma comunidade divertida para todos. No caso de alguém violar o Código de Conduta de Juxt, iremos tomar as medidas necessárias que poderão incluir banir o utilizador ou a consola.", + "twelfth": "Já jogaste este jogo?", + "thirteenth": "Se publicares numa comunidade de um jogo que já jogaste, as tuas publicações irão incluir um ícone que indica que já o jogaste." + }, + "experience": "Experiência de jogo", + "experience_text": { + "info": "Fala-nos um pouco da tua experiência com jogos. Podes alterar estas definições mais tarde.", + "beginner": "Iniciante", + "intermediate": "Intermédio", + "expert": "Avançado" + }, + "google": "Google Analytics", + "google_text": "Juxtaposition usa Google Analytics de forma a recolher informações de como os utilizadores usam o serviço. Os dados recolhidos incluem, mas não estão limitados a, hora da visita, páginas visitadas e tempo passado no site. Estes dados não estão associados a ti de maneira nenhuma e não são usados para anúncios por nós nem pela Google.", + "ready": "Pronto para usar Juxt", + "ready_text": "Começa por explorar algumas comunidades e ver o que as pessoas de todo o mundo estão a publicar. Aproveita esta oportunidade para te familiarizares com Juxt. Pode ser que descubras algo novo pelo caminho!", + "done": "Diverte-te com Juxt!", + "done_button": "Vamos a isso!", + "guest": "Modo de convidado", + "guest_text": "Não nos foi possível verificar a tua conta de momento, o que provavelmente significa que tens sessão iniciada com uma conta Nintendo Network. Podes explorar Juxt, mas não te será possível marcar publicações com Fixe! ou fazer publicações até que inicies sessão com uma conta Pretendo Network. Para mais informações visita o nosso servidor Discord.", + "guest_button": "Compreendo" + } } diff --git a/apps/juxtaposition-ui/src/translations/pt_PT.json b/apps/juxtaposition-ui/src/translations/pt_PT.json new file mode 100644 index 00000000..5e93b429 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/pt_PT.json @@ -0,0 +1,126 @@ +{ + "language": "Português", + "global": { + "user_page": "Perfil do Utilizador", + "activity_feed": "Atividade", + "communities": "Comunidades", + "messages": "Mensagens", + "notifications": "Notificações", + "go_back": "Voltar", + "back": "Anterior", + "yeahs": "Fixes", + "more": "Carregar mais publicações", + "no_posts": "Sem publicações", + "private": "Privado", + "close": "Fechar", + "save": "Guardar", + "exit": "Sair", + "next": "Seguinte" + }, + "all_communities": { + "text": "Todas as comunidades", + "announcements": "Anúncios", + "ann_string": "Clica aqui para veres os anúncios recentes!", + "popular_places": "Lugares Populares", + "new_communities": "Novas Comunidades", + "show_all": "Mostrar todas", + "search": "Procurar comunidades..." + }, + "community": { + "recent": "Publicações Recentes", + "follow": "Seguir Comunidade", + "following": "A seguir", + "followers": "Seguidores", + "posts": "Publicações", + "tags": "Tags", + "popular": "Publicações Populares", + "verified": "Publicações Verificadas", + "new": "Nova Publicação" + }, + "user_page": { + "country": "País", + "birthday": "Aniversário", + "game_experience": "Experiência de Jogo", + "posts": "Publicações", + "followers": "Seguidores", + "follow_user": "Seguir", + "following_user": "Seguidores", + "friends": "Amigos", + "following": "A seguir", + "befriend": "Tornar-se Amigo", + "pending": "Pendente", + "unfriend": "Remover Amizade", + "no_friends": "Sem Amigos", + "no_following": "Não segue ninguém", + "no_followers": "Sem seguidores" + }, + "notifications": { + "none": "Sem notificações.", + "new_follower": "seguiu-te!" + }, + "setup": { + "rules_text": { + "fifth": "De forma a mantermos Juxt um lugar divertido para todos, pedimos-te que penses nas outras pessoas. Ajuda-nos a manter Juxt uma boa experiência ao não fazer publicações impróprias ou ofensivas.", + "sixth": "Não partilhes informações pessoais", + "third": "Juxt contém várias comunidades de jogadores onde pessoas de todo o mundo podem partilhar as suas ideias. Quando publicas numa comunidade, lembra-te que todos podem ver as tuas publicações, por isto expressa-te de uma maneira que possa ser divertida para todos. Usa o senso comum e pensa antes de publicar. Juxt é um serviço que também está acessível pela Internet, por isto pessoas que não usam Juxt também podem ver as tuas publicações. Adicionalmente, quaisquer comentários que faças em publicações de amigos são visíveis não só por estes, mas também por pessoas de todo o mundo. Tem isto em consideração.", + "first": "Estas são algumas regras que permitem que Juxt seja uma experiência divertida para todos. O código de conduta Juxt contém informação detalhada, por isso lê-o com atenção.", + "second": "Publicações podem ser vistas em todo o mundo", + "fourth": "Sê simpático com os outros", + "tenth": "Violações do código de conduta", + "seventh": "Lembra-te, conhecer alguém em Juxt não é o mesmo que conhecer alguém na vida real. Nunca partilhes com ninguém em Juxt o teu endereço de e-mail, morada, trabalho, nome da escola ou qualquer outra informação que te possa identificar nem partilhes informação de outras pessoas. Adicionalmente, se alguém que conheceres em Juxt te convidar para que se conheçam no mundo real, não aceites. Juxt é uma comunidade online e não deve ser usada para marcar encontros presenciais.", + "eighth": "Não publiques Spoilers", + "ninth": "Algumas pessoas vêm a Juxt à procura de dicas e truques para jogos, mas outras preferem descobrir os jogos por si mesmas. Publicações que revelam segredos de um jogo ou a sua história são chaamdas de \"spoilers.\" Se vais publicar algo sobre um jogo que pode ser um spoiler, marca a respetiva caixa antes de enviares a tua publicação. Desta forma, pessoas que não querem ver spoilers não verão a tua publicação.", + "eleventh": "O nosso objetivo é fazer de Juxt uma comunidade divertida para todos. No caso de alguém violar o Código de Conduta de Juxt, iremos tomar as medidas necessárias que poderão incluir banir o utilizador ou a consola.", + "twelfth": "Já jogaste este jogo?", + "thirteenth": "Se publicares numa comunidade de um jogo que já jogaste, as tuas publicações irão incluir um ícone que indica que já o jogaste." + }, + "welcome": "Bem-vindo a Juxtaposition!", + "welcome_text": "Juxt é uma comunidade de jogadores que liga pessoas de todo o mundo utilizando personagens Mii. Usa Juxt para partilhar as tuas experiências de jogo e conhecer novas pessoas de todo o mundo.", + "beta": "Nota sobre Beta", + "beta_text": { + "first": "Estás prestes a experimentar a primeira versão beta pública de Juxt. Isto significa que ainda há muito a fazer e que muitas coisas ainda poderão mudar a qualquer momento.", + "second": "No final do período beta, os dados registados na plataforma poderão vir a ser apagados.", + "third": "Este website, o seu software e todos os conteúdos nele conteúdos são disponibilizados “tal como estão” e “quando disponíveis”. A Pretendo Network não oferece quaisquer garantias, quer expressas quer implícitas, a respeito da integridade ou disponibilidade do website, do seu software ou dos seus conteúdos." + }, + "info": "O que é Juxtaposition?", + "info_text": "Juxt é uma comunidade de utilizadores que liga pessoas\n de todo o mundo. Podes escrever ou desenhar publicações nas comunidades dos jogos ou enviar mensagens diretamente aos teus amigos.", + "rules": "Regras de Juxt", + "google_text": "Juxtaposition usa Google Analytics de forma a recolher informações de como os utilizadores usam o serviço. Os dados recolhidos incluem, mas não estão limitados a, hora da visita, páginas visitadas e tempo passado no site. Estes dados não estão associados a ti de maneira nenhuma e não são usados para anúncios por nós nem pela Google.", + "experience": "Experiência de jogo", + "experience_text": { + "info": "Fala-nos um pouco da tua experiência com jogos. Podes alterar estas definições mais tarde.", + "beginner": "Iniciante", + "intermediate": "Intermédio", + "expert": "Avançado" + }, + "google": "Google Analytics", + "ready": "Pronto para usar Juxt", + "ready_text": "Começa por explorar algumas comunidades e ver o que as pessoas de todo o mundo estão a publicar. Aproveita esta oportunidade para te familiarizares com Juxt. Pode ser que descubras algo novo pelo caminho!", + "done": "Diverte-te com Juxt!", + "done_button": "Vamos a isso!", + "guest": "Modo de convidado", + "guest_text": "Não nos foi possível verificar a tua conta de momento, o que provavelmente significa que tens sessão iniciada com uma conta Nintendo Network. Podes explorar Juxt, mas não te será possível marcar publicações com Fixe! ou fazer publicações até que inicies sessão com uma conta Pretendo Network. Para mais informações visita o nosso servidor Discord.", + "guest_button": "Compreendo" + }, + "user_settings": { + "profile_settings": "Preferências de Perfil", + "show_country": "Mostrar país no perfil", + "show_birthday": "Mostrar data de aniversário no perfil", + "show_game": "Mostrar experiência de jogo no perfil", + "show_comment": "Mostrar comentário no perfil", + "profile_comment": "Comentário do Perfil", + "swearing": "O comentário do perfil não pode ter linguagem obscena." + }, + "activity_feed": { + "empty": "Isto está muito vazio. Começa a seguir alguém!" + }, + "new_post": { + "new_post_text": "Nova publicação", + "post_to": "Publicar em", + "text_hint": "Toca aqui para criar uma publicação.", + "swearing": "A publicação não pode ter linguagem obscena." + }, + "messages": { + "coming_soon": "A funcionalidade de mensagens ainda não está disponível. Volta em breve!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/ro.json b/apps/juxtaposition-ui/src/translations/ro.json index eed8e4f7..fb973113 100644 --- a/apps/juxtaposition-ui/src/translations/ro.json +++ b/apps/juxtaposition-ui/src/translations/ro.json @@ -43,9 +43,9 @@ "game_experience": "Experiență", "posts": "Postări", "friends": "Prieteni", - "following": "Urmărește", + "following": "Următor", "followers": "Urmăritori", - "follow_user": "Urmărește", + "follow_user": "Următor", "following_user": "Urmărești deja", "befriend": "Deveniți prieteni", "unfriend": "Șterge prieten", @@ -86,7 +86,7 @@ }, "info": "Ce e Juxtaposition?", "welcome_text": "Juxt este o comunitate de jocuri care conectează oamenii din jurul lumii folosind personaje Mii. Folosește Juxt pentru a-ți împărtăși experiențele tale din jocuri și a cunoaște oameni din toată lumea.", - "info_text": "Juxt este o comunitate de jocuri de noroc condusă de utilizatori, unde puteți interacționa cu oameni din întreaga lume. Puteți scrie sau desena postări în comunitățile de jocuri sau puteți trimite mesaje direct prietenilor dumneavoastră.", + "info_text": "Juxt este o comunitate de jocuri condusă de utilizatori, unde puteți interacționa cu oamenii\n peste tot în lume. Puteți scrie sau desena postări în comunitățile de jocuri sau puteți trimite mesaje direct prietenilor.", "rules": "Regurile Juxt", "rules_text": { "first": "Următoarele sunt câteva orientări importante pentru a face din Juxt o experiență plăcută și distractivă pentru toată lumea. Codul de conduită Juxt conține informații detaliate, așa că vă rugăm să îl citiți cu atenție.", diff --git a/apps/juxtaposition-ui/src/translations/sk.json b/apps/juxtaposition-ui/src/translations/sk.json new file mode 100644 index 00000000..c88ddbc0 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/sk.json @@ -0,0 +1,126 @@ +{ + "language": "Slovenčina", + "global": { + "communities": "Komunity", + "notifications": "Pripomienky", + "private": "Súkromné", + "close": "Zavrieť", + "save": "Uložiť", + "exit": "Ukončiť", + "next": "Ďalší", + "no_posts": "Žiadne príspevky", + "messages": "Správy", + "user_page": "Používateľská stránka", + "activity_feed": "Informačný kanál aktivity", + "go_back": "Choď späť", + "back": "Späť", + "more": "Načítať ďalšie príspevky", + "yeahs": "" + }, + "community": { + "following": "Odoberané", + "followers": "Odoberatelia", + "posts": "Príspevky", + "recent": "Čerstvé príspevky", + "popular": "Populárne príspevky", + "verified": "Overené príspevky", + "new": "Nové príspevky", + "follow": "Odoberané komunity", + "tags": "Tagy" + }, + "user_page": { + "country": "Krajina", + "friends": "Priatelia", + "following": "Odoberané", + "followers": "Odoberatelia", + "follow_user": "Odoberať", + "following_user": "Odoberané", + "unfriend": "Odstrániť priateľa", + "no_friends": "Žiadny priatelia", + "posts": "Príspevky", + "birthday": "Dátum narodenia", + "no_followers": "Žiadni odoberatelia", + "game_experience": "Herný zážitok", + "befriend": "Spriateliť sa", + "pending": "Čaká na spracovanie", + "no_following": "Nikoho nesledujem" + }, + "all_communities": { + "text": "Všetky komunity", + "popular_places": "Populárne miesta", + "show_all": "Ukázať všetky", + "new_communities": "Nové komunity", + "announcements": "Oznamy", + "ann_string": "Kliknutím sem zobrazíte najnovšie oznámenia!", + "search": "Hľadať komunity..." + }, + "user_settings": { + "profile_settings": "Nastavenia profilu", + "show_country": "Ukázať krajinu na profile", + "show_birthday": "Ukázať dátum narodenia na profile", + "show_game": "Zobraziť herný zážitok na profile", + "show_comment": "Zobraziť komentár k profilu", + "profile_comment": "Komentár k profilu", + "swearing": "Komentár profilu nemôže obsahovať explicitné výrazy." + }, + "notifications": { + "new_follower": "vás odoberá!", + "none": "Žiadne pripomienky." + }, + "new_post": { + "text_hint": "Ťuknite sem na vytvorenie príspevku.", + "swearing": "Príspevok nemôže obsahovať nadávky.", + "new_post_text": "Nový príspevok", + "post_to": "Uverejniť na" + }, + "setup": { + "welcome": "Vitajte v Juxtaposition!", + "info": "Čo je Juxtaposition?", + "rules": "Pravidlá Juxta", + "rules_text": { + "eighth": "Neposielajte spoilery", + "third": "Juxt obsahuje mnoho herných komunít, kde môžu ľudia z celého sveta zdieľať svoje myšlienky. Keď uverejňujete príspevok v komunite, pamätajte na to, že ho môže vidieť každý, preto sa prosím vyjadrite spôsobom, ktorý bude baviť každého. Použite zdravý rozum a premýšľajte skôr, ako uverejníte príspevok. Juxt je služba, ktorá je dostupná aj z internetu, takže majte na pamäti, že vaše príspevky môžu vidieť aj ľudia, ktorí nepoužívajú Juxt. Navyše, všetky komentáre, ktoré pridáte k príspevkom svojich priateľov, uvidia nielen vaši priatelia, ale aj ľudia na celom svete. Majte to prosím na pamäti.", + "seventh": "Pamätajte, že poznať niekoho v Juxte nie je to isté ako poznať ho v skutočnom živote. Nikdy nezdieľajte svoju e-mailovú adresu, domácu adresu, meno do práce alebo školy ani iné osobné identifikačné údaje s nikým na Juxt a nikdy nezdieľajte informácie nikoho iného. Navyše, ak vás niekto, koho stretnete v Juxte, pozve, aby ste sa s ním stretli v skutočnom svete, neprijmite ho. Juxt je online komunita a nemala by sa používať na organizovanie stretnutí v reálnom svete.", + "ninth": "Niektorí ľudia prichádzajú do Juxtu hľadať tipy a triky pre hry, no iní chcú objaviť tajomstvá hry úplne sami. Príspevky, ktoré odhaľujú tajomstvá hry alebo jej príbeh, sa nazývajú „spoilery“. Ak uverejňujete niečo o hre, ktorá by mohla byť spoilerom, pred odoslaním príspevku nezabudnite zaškrtnúť políčko Spoilers. Týmto spôsobom ľudia, ktorí nechcú byť rozmaznaní, neuvidia váš príspevok.", + "first": "Nasleduje niekoľko dôležitých pokynov na to, aby bol Juxt zábavný a príjemný pre každého. Etický kódex Juxt obsahuje podrobné informácie, preto si ho pozorne prečítajte.", + "second": "Príspevky je možné prezerať po celom svete", + "fourth": "Buďte k sebe milí", + "fifth": "Aby bol Juxt zábavným miestom pre každého, žiadame vás, aby ste boli ohľaduplní k ostatným používateľom. Pomôžte nám udržať Juxt príjemný zážitok tým, že nebudete uverejňovať nič nevhodné alebo urážlivé.", + "sixth": "Neuverejňujte osobné informácie – vaše alebo iných", + "tenth": "Porušenie kódexu správania", + "eleventh": "Naším cieľom je, aby bol Juxt zábavný a príjemný pre každého. V prípade, že niekto poruší Etický kódex Juxt, podnikneme príslušné kroky, vrátane zablokovania porušujúceho používateľa alebo konzoly.", + "twelfth": "Hrali ste túto hru?", + "thirteenth": "Ak uverejňujete príspevok v komunite pre hru, ktorú ste hrali, vaše príspevky budú mať ikonu označujúcu, že ste ju hrali." + }, + "experience_text": { + "beginner": "Začiatočník", + "expert": "Expert", + "info": "Povedzte nám, ako by ste opísali úroveň svojich skúseností s hrami. Toto nastavenie môžete neskôr zmeniť.", + "intermediate": "Stredne pokročilý" + }, + "guest_button": "Rozumiem", + "ready": "Pripavený začať používať Juxt", + "done_button": "Poďme do toho!", + "guest": "Režim návštevníka", + "beta_text": { + "first": "Chystáte sa vyskúšať prvú verejnú beta verziu Juxtu. To znamená, že veľa je stále vo vzduchu a veľa sa môže kedykoľvek zmeniť.", + "third": "Webová lokalita, jej softvér a všetok obsah, ktorý sa na nej nachádza, sú poskytované „tak ako sú“ a „ako sú dostupné“. Sieť Pretendo neposkytuje žiadne záruky, či už výslovné alebo predpokladané, pokiaľ ide o vhodnosť alebo použiteľnosť webovej stránky, jej softvéru alebo akéhokoľvek obsahu.", + "second": "To môže a môže zahŕňať úplné vymazanie databázy na konci alebo počas beta obdobia." + }, + "welcome_text": "Juxt je herná komunita, ktorá spája ľudí z celého sveta pomocou postavičiek Mii. Pomocou Juxt môžete zdieľať svoje herné zážitky a stretnúť sa s ľuďmi z celého sveta.", + "beta": "Vyhlásenie beta verzie", + "info_text": "Juxt je herná komunita riadená používateľmi, kde môžete komunikovať s ľuďmi\n po celom svete. Môžete písať alebo kresliť príspevky v herných komunitách alebo posielať správy priamo svojim priateľom.", + "ready_text": "Najprv si pozrite niektoré komunity a zistite, o čom píšu ľudia z celého sveta. Využite túto príležitosť a zoznámte sa s Juxtom. Na ceste môžete urobiť nejaké nové objavy!", + "experience": "Herný zážitok", + "google": "Google Analytics", + "google_text": "Juxtaposition používa Google Analytics na sledovanie toho, ako používatelia používajú našu službu. Zhromaždené údaje zahŕňajú, ale nie sú obmedzené na čas návštevy, navštívené stránky a čas strávený na stránke. Tieto údaje sa k vám žiadnym spôsobom nepripájajú a nepoužívajú sa ani my, ani spoločnosť Google.", + "done": "Bavte sa v Juxte!", + "guest_text": "Momentálne nemôžeme overiť váš účet, čo pravdepodobne znamená, že sa momentálne prihlasujete pomocou účtu Nintendo Network. Stále budete môcť prehliadať Juxt, ale nebudete môcť Áno! príspevky alebo vytvorte vlastné príspevky, kým sa neprihlásite pomocou účtu Pretendo Network. Pre viac informácií skontrolujte Discord server." + }, + "activity_feed": { + "empty": "Je to tu prázdne. Skúste niekoho odoberať!" + }, + "messages": { + "coming_soon": "Správy ešte nie sú pripravené. Vráťte sa sem čoskoro!" + } +} diff --git a/apps/juxtaposition-ui/src/translations/sr.json b/apps/juxtaposition-ui/src/translations/sr.json index 836e50f5..2fb87008 100644 --- a/apps/juxtaposition-ui/src/translations/sr.json +++ b/apps/juxtaposition-ui/src/translations/sr.json @@ -11,7 +11,10 @@ "next": "Sledeće", "go_back": "Vrati se", "close": "Zatvori", - "more": "Učitaj još objava" + "more": "Učitaj još objava", + "yeahs": "", + "activity_feed": "", + "user_page": "" }, "language": "Srpski", "community": { @@ -19,7 +22,7 @@ "follow": "Zaprati zajednicu", "following": "Praćenje", "followers": "Pratioci", - "posts": "Objave", + "posts": "Knjiži", "tags": "Oznake", "recent": "Nedavne objave", "popular": "Popularne objave", @@ -29,11 +32,18 @@ "birthday": "Rođendan", "following_user": "Praćenje", "country": "Zemlja", - "posts": "Objave", + "posts": "Knjiži", "friends": "Prijatelji", "follow_user": "Zaprati", "followers": "Pratioci", - "pending": "U toku" + "pending": "U toku", + "game_experience": "", + "no_following": "", + "befriend": "", + "unfriend": "", + "no_followers": "", + "no_friends": "", + "following": "" }, "all_communities": { "new_communities": "Nove zajednice", @@ -51,12 +61,17 @@ "user_settings": { "profile_settings": "Podešavanja profila", "show_country": "Prikaži zemlju na profilu", - "show_birthday": "Prikaži rođendan na profilu" + "show_birthday": "Prikaži rođendan na profilu", + "show_game": "", + "show_comment": "", + "profile_comment": "", + "swearing": "" }, "new_post": { "new_post_text": "Nova objava", "post_to": "Objavi na", - "text_hint": "Dodirni ovde da napraviš objavu." + "text_hint": "Dodirni ovde da napraviš objavu.", + "swearing": "" }, "activity_feed": { "empty": "Ovde je prazno. Pokušaj da pratiš nekoga!" @@ -66,11 +81,46 @@ }, "setup": { "rules_text": { - "eighth": "Ne objavljuj spojlere" + "eighth": "Ne objavljuj spojlere", + "eleventh": "", + "tenth": "", + "third": "", + "thirteenth": "", + "fifth": "", + "ninth": "", + "fourth": "", + "second": "", + "sixth": "", + "first": "", + "seventh": "", + "twelfth": "" }, "experience_text": { "beginner": "Početnik", - "intermediate": "Srednji" - } + "intermediate": "Srednji", + "expert": "Ekspert", + "info": "Molimo vas da opišete vaš nivo iskustva sa igricama. Ovo podešavanje se može promeniti kasnije." + }, + "beta_text": { + "second": "Ovo može podrazumevati brisanje cele baze podataka po završetku ili za vreme probnog perioda.", + "third": "", + "first": "" + }, + "experience": "Iskustvo sa Igricama", + "google": "Google Analitika", + "google_text": "Juxtaposition koristi Google Analitiku kako bi pratilo kako korisnici koriste naš servis. Prikupljene informacije uključuju ali nisu ograničene na vreme posete, posećene strane i vreme provedeno na sajtu. Ovi podatci nisu lično asocirani i neće biti korišćene u svrhe reklama od strane nas ili Google-a.", + "ready": "Spremni da Koristite Juxt", + "done": "Uživajte u Juxt-u!", + "done_button": "Krenimo!", + "ready_text": "Prvo proverite zajednice i vidite o čemu ljudi objavljuju oko celog sveta. Iskoristite ovu priliku da se upoznate sa Juxt-om. Možda ćete napraviti zanimljiva otkrića!", + "guest_button": "", + "guest": "", + "guest_text": "", + "rules": "", + "welcome_text": "", + "info": "", + "info_text": "", + "beta": "", + "welcome": "" } } diff --git a/apps/juxtaposition-ui/src/translations/sv.json b/apps/juxtaposition-ui/src/translations/sv.json index a595e796..b11835c9 100644 --- a/apps/juxtaposition-ui/src/translations/sv.json +++ b/apps/juxtaposition-ui/src/translations/sv.json @@ -57,15 +57,15 @@ "close": "Stäng", "save": "Spara", "no_posts": "Inga Inlägg", - "yeahs": "Jan", + "yeahs": "Ja:n", "private": "Privat", "more": "Ladda Mer Inlägg" }, "all_communities": { - "text": "Alla Samhällen", + "text": "Alla Gemenskap", "announcements": "Uttalanden", - "new_communities": "Nya Samhällen", - "search": "Sök samhällen...", + "new_communities": "Nya gemenskap", + "search": "Sök gemenskap...", "show_all": "Visa Allt", "popular_places": "Populära Ställen", "ann_string": "Klicka här för att visa de nyligen skapade uttalandena!" @@ -82,7 +82,7 @@ "community": { "following": "Följer", "tags": "Taggar", - "follow": "Följ Samhälle", + "follow": "Följ gemenskap", "posts": "Inlägg", "followers": "Följare", "verified": "Verifierade Inlägg", diff --git a/apps/juxtaposition-ui/src/translations/ta.json b/apps/juxtaposition-ui/src/translations/ta.json new file mode 100644 index 00000000..731522f3 --- /dev/null +++ b/apps/juxtaposition-ui/src/translations/ta.json @@ -0,0 +1,126 @@ +{ + "setup": { + "info": "சுருக்கம் என்றால் என்ன?", + "rules": "Juxts விதிகள்", + "guest_button": "எனக்குப் புரிகிறது", + "welcome": "சுருக்கத்திற்கு வருக!", + "welcome_text": "MIIXT என்பது ஒரு கேமிங் சமூகம், இது உலகெங்கிலும் உள்ள மக்களை MII எழுத்துக்களைப் பயன்படுத்தி இணைக்கிறது. உங்கள் கேமிங் அனுபவங்களைப் பகிர்ந்து கொள்ளவும், உலகெங்கிலும் உள்ளவர்களைச் சந்திக்கவும் சாக்ச்டைப் பயன்படுத்தவும்.", + "beta": "பீட்டா மறுப்பு", + "beta_text": { + "second": "இது முடிவில் அல்லது பீட்டா காலகட்டத்தில் தரவுத்தளத்தின் மொத்த துடைப்பைக் கொண்டிருக்கலாம்.", + "third": "வலைத்தளம், அதன் மென்பொருள் மற்றும் அதில் காணப்படும் அனைத்து உள்ளடக்கங்களும் “இருப்பது” மற்றும் “கிடைக்கக்கூடிய” அடிப்படையில் வழங்கப்படுகின்றன. வலைத்தளத்தின், அதன் மென்பொருள் அல்லது அதன் எந்தவொரு உள்ளடக்கத்தின் பொருந்தக்கூடிய தன்மை அல்லது பயன்பாட்டினைப் பொறுத்தவரை, எக்ச்பிரச் அல்லது மறைமுகமாக இருந்தாலும் ப்ரெடெண்டோ பிணையம் எந்த உத்தரவாதங்களையும் கொடுக்கவில்லை.", + "first": "நீங்கள் முதல் பொது பீட்டாவை முயற்சிக்க உள்ளீர்கள். இதன் பொருள் நிறைய காற்றில் உள்ளது, மேலும் எந்த நேரத்திலும் நிறைய மாறலாம்." + }, + "rules_text": { + "first": "அனைவருக்கும் வேடிக்கையான மற்றும் சுவையான அனுபவமாக மாற்றுவதற்கான சில முக்கியமான வழிகாட்டுதல்கள் பின்வருமாறு. நடத்தை நெறிமுறையில் விரிவான தகவல்கள் உள்ளன, எனவே தயவுசெய்து அதைக் கவனமாகப் படியுங்கள்.", + "second": "இடுகைகளை உலகம் முழுவதும் காணலாம்", + "third": "உலகெங்கிலும் உள்ள மக்கள் தங்கள் எண்ணங்களைப் பகிர்ந்து கொள்ளக்கூடிய பல கேமிங் சமூகங்கள் உள்ளன. நீங்கள் ஒரு சமூகத்தில் இடுகையிடும்போது, எல்லோரும் அதைப் பார்க்க முடியும் என்பதை நினைவில் கொள்ளுங்கள், எனவே எல்லோரும் அனுபவிக்கக்கூடிய வகையில் உங்களை வெளிப்படுத்துங்கள். பொது அறிவைப் பயன்படுத்துங்கள், நீங்கள் இடுகையிடுவதற்கு முன் சிந்தியுங்கள். JUXT என்பது இணையத்திலிருந்து அணுகக்கூடிய ஒரு சேவையாகும், எனவே உங்கள் இடுகைகளைப் பயன்படுத்தாதவர்களும் பார்க்கக்கூடும் என்பதை நினைவில் கொள்ளுங்கள். கூடுதலாக, உங்கள் நண்பர்களின் இடுகைகளில் நீங்கள் செய்யும் எந்தவொரு கருத்துகளும் உங்கள் நண்பர்களால் மட்டுமல்ல, உலகெங்கிலும் உள்ளவர்களால் பார்க்கப்படும். இதை மனதில் கொள்ளுங்கள்.", + "fourth": "ஒருவருக்கொருவர் நன்றாக இருங்கள்", + "fifth": "அனைவருக்கும் ஒரு வேடிக்கையான இடத்தை வைத்திருக்க, நீங்கள் மற்ற பயனர்களிடம் அக்கறை காட்டுமாறு கேட்டுக்கொள்கிறோம். பொருத்தமற்ற அல்லது தாக்குதல் எதையும் இடுகையிடாமல் ஒரு சுவையான அனுபவத்தை வைத்திருக்க எங்களுக்கு உதவுங்கள்.", + "eighth": "ச்பாய்லர்களை இடுகையிட வேண்டாம்", + "tenth": "நடத்தை விதிமுறை மீறல்கள்", + "twelfth": "நீங்கள் அந்த விளையாட்டை விளையாடியிருக்கிறீர்களா?", + "sixth": "தனிப்பட்ட தகவல்களை இடுகையிட வேண்டாம் - உங்கள் அல்லது பிறர் உடையது", + "eleventh": "அனைவருக்கும் வேடிக்கையாகவும் சுவாரச்யமாகவும் இருப்பதே எங்கள் குறிக்கோள். யாரோ ஒருவர் நடத்தை விதிமுறைகளை மீறும்போது, புண்படுத்தும் பயனர் அல்லது கன்சோலைத் தடுப்பது உட்பட, நாங்கள் தகுந்த நடவடிக்கை எடுப்போம்.", + "seventh": "நினைவில் கொள்ளுங்கள், ஒருவரைத் தெரிந்துகொள்வது நிச வாழ்க்கையில் அவர்களை அறிந்து கொள்வதற்கு சமமானதல்ல. உங்கள் மின்னஞ்சல் முகவரி, வீட்டு முகவரி, வேலை அல்லது பள்ளி பெயர் அல்லது பிற தனிப்பட்ட முறையில் அடையாளம் காணும் பிற தகவல்களை ஒருபோதும் பகிர வேண்டாம், வேறு யாருடைய தகவலையும் பகிர வேண்டாம். கூடுதலாக, நீங்கள் சந்திக்கும் ஒருவர் உண்மையான உலகில் அவரை அல்லது அவளைச் சந்திக்க உங்களை அழைத்தால், ஏற்றுக்கொள்ள வேண்டாம். JUXT என்பது ஒரு நிகழ்நிலை சமூகம் மற்றும் நிச உலக சந்திப்புகளை ஏற்பாடு செய்யப் பயன்படுத்தக் கூடாது.", + "ninth": "சிலர் விளையாட்டுகளுக்கான உதவிக்குறிப்புகள் மற்றும் தந்திரங்களைத் தேடி வருகிறார்கள், ஆனால் மற்றவர்கள் ஒரு விளையாட்டின் ரகசியங்களைச் சொந்தமாகக் கண்டுபிடிக்க விரும்புகிறார்கள். ஒரு விளையாட்டின் ரகசியங்களை அல்லது அதன் கதையை வெளிப்படுத்தும் இடுகைகள் \"ச்பாய்லர்கள்\" என்று அழைக்கப்படுகின்றன. ச்பாய்லராக இருக்கும் ஒரு விளையாட்டைப் பற்றி நீங்கள் ஏதாவது இடுகையிடுகிறீர்கள் என்றால், உங்கள் இடுகையை அனுப்புவதற்கு முன்பு ச்பாய்லர்கள் பெட்டியைச் சரிபார்க்கவும். இந்த வழியில், கெட்டுப்போக விரும்பாதவர்கள் உங்கள் இடுகையைப் பார்க்கமாட்டார்கள்.", + "thirteenth": "நீங்கள் விளையாடிய ஒரு விளையாட்டுக்காகச் சமூகத்தில் இடுகையிடுகிறீர்கள் என்றால், உங்கள் இடுகைகளில் நீங்கள் அதை விளையாடியதைக் குறிக்கும் படவுரு இருக்கும்." + }, + "experience": "விளையாட்டு பட்டறிவு", + "experience_text": { + "info": "விளையாட்டுகளுடனான உங்கள் அனுபவத்தை நீங்கள் எவ்வாறு விவரிப்பீர்கள் என்பதை எங்களுக்குத் தெரிவிக்கவும். இந்த அமைப்பை நீங்கள் பின்னர் மாற்றலாம்.", + "intermediate": "இடைநிலை", + "expert": "வல்லுநர்", + "beginner": "தொடக்கம்" + }, + "google": "கூகிள் பகுப்பாய்வியல்", + "google_text": "பயனர்கள் எங்கள் சேவையை எவ்வாறு பயன்படுத்துகிறார்கள் என்பதைக் கண்டறிய Google Analytics ஐப் பயன்படுத்துகிறது. சேகரிக்கப்பட்ட தரவுகளில் வருகை நேரம், பார்வையிட்ட பக்கங்கள் மற்றும் தளத்தில் செலவழித்த நேரம் ஆகியவை அடங்கும். இந்தத் தரவு உங்களுடன் எந்த வகையிலும் இணைக்கப்படவில்லை, மேலும் இது அமெரிக்கா அல்லது கூகிள் விளம்பரங்களுக்குப் பயன்படுத்தப்படவில்லை.", + "ready": "JUXT ஐப் பயன்படுத்தத் தயாராக உள்ளது", + "ready_text": "முதலில் சில சமூகங்களைப் பார்த்து, உலகம் முழுவதிலுமிருந்து மக்கள் எதைப் பற்றி இடுகையிடுகிறார்கள் என்பதைப் பாருங்கள். இந்த வாய்ப்பை நீங்களே பழக்கப்படுத்திக்கொள்ள இந்த வாய்ப்பைப் பயன்படுத்துங்கள். நீங்கள் சில புதிய கண்டுபிடிப்புகளைச் செய்யலாம்!", + "done": "வேடிக்கையாக இருங்கள்!", + "done_button": "போகலாம்!", + "guest": "விருந்தினர் பயன்முறை", + "info_text": "JUXT ஒரு பயனர் உந்துதல் கேமிங் சமூகமாகும், அங்கு நீங்கள் மக்களுடன் தொடர்பு கொள்ளலாம்\n உலகம் முழுவதும். நீங்கள் விளையாட்டு சமூகங்களில் இடுகைகளை எழுதலாம் அல்லது வரையலாம் அல்லது உங்கள் நண்பர்களுக்கு நேரடியாகச் செய்திகளை அனுப்பலாம்.", + "guest_text": "இந்த நேரத்தில் உங்கள் கணக்கை எங்களால் சரிபார்க்க முடியாது, அதாவது நீங்கள் தற்போது ஒரு நிண்டெண்டோ பிணையம் கணக்கில் உள்நுழைகிறீர்கள். நீங்கள் இன்னும் சாக்ச்ட்டை உலாவ முடியும், ஆனால் நீங்கள் ஆம் என்று முடியாது! ப்ரெடெண்டோ பிணையம் கணக்கில் உள்நுழையும் வரை இடுகைகள் அல்லது உங்கள் சொந்த இடுகைகளை உருவாக்கவும். மேலும் தகவலுக்கு டிச்கார்ட் சேவையகத்தைச் சரிபார்க்கவும்." + }, + "global": { + "user_page": "பயனர் பக்கம்", + "activity_feed": "செயல்பாட்டு ஊட்டம்", + "communities": "சமூகங்கள்", + "messages": "செய்திகள்", + "notifications": "அறிவிப்புகள்", + "back": "பின்", + "yeahs": "ஆம்", + "go_back": "திரும்பிச் செல்", + "more": "மேலும் இடுகைகளை ஏற்றவும்", + "no_posts": "பதிவுகள் இல்லை", + "private": "தனிப்பட்ட", + "close": "மூடு", + "save": "சேமி", + "exit": "வெளியேறு", + "next": "அடுத்தது" + }, + "all_communities": { + "text": "அனைத்து சமூகங்களும்", + "announcements": "அறிவிப்புகள்", + "ann_string": "அண்மைக் கால அறிவிப்புகளைக் காண இங்கே சொடுக்கு செய்க!", + "new_communities": "புதிய சமூகங்கள்", + "show_all": "அனைத்தையும் காட்டு", + "popular_places": "பெயர்பெற்ற இடங்கள்", + "search": "சமூகங்களைத் தேடு..." + }, + "community": { + "follow": "சமூகத்தைப் பின்பற்றுங்கள்", + "followers": "பின்தொடர்பவர்கள்", + "posts": "இடுகைகள்", + "tags": "குறிச்சொற்கள்", + "recent": "அண்மைக் கால இடுகைகள்", + "following": "பின்தொடர்கிறது", + "verified": "சரிபார்க்கப்பட்ட பதிவுகள்", + "new": "புதிய இடுகை", + "popular": "பெயர்பெற்ற இடுகைகள்" + }, + "user_page": { + "country": "நாடு", + "birthday": "பிறந்த நாள்", + "game_experience": "விளையாட்டு பட்டறிவு", + "posts": "இடுகைகள்", + "friends": "நண்பர்கள்", + "followers": "பின்தொடர்பவர்கள்", + "follow_user": "பின்தொடர்", + "befriend": "நட்பு", + "pending": "நிலுவையில் உள்ளது", + "no_friends": "நண்பர்கள் இல்லை", + "following": "பின்தொடர்கிறது", + "following_user": "பின்தொடர்கிறது", + "no_following": "யாரையும் பின்பற்றவில்லை", + "no_followers": "பின்தொடர்பவர்கள் இல்லை", + "unfriend": "நட்புவிலக்கம்" + }, + "user_settings": { + "show_country": "தன்விவரக்குறிப்பில் நாட்டைக் காட்டு", + "show_birthday": "தன்விவரக்குறிப்பில் பிறந்தநாளைக் காட்டு", + "show_game": "தன்விவரக்குறிப்பில் விளையாட்டு அனுபவத்தைக் காட்டு", + "profile_comment": "தன்விவரக்குறிப்பு கருத்து", + "show_comment": "தன்விவரக்குறிப்பில் கருத்தைக் காட்டு", + "swearing": "தன்விவரக்குறிப்புக் கருத்தில் வெளிப்படையான மொழியைக் கொண்டிருக்க முடியாது.", + "profile_settings": "தன்விவரக்குறிப்பு அமைப்புகள்" + }, + "activity_feed": { + "empty": "இது இங்கே காலியாக உள்ளது. யாரையாவது பின்தொடர முயற்சிக்கவும்!" + }, + "notifications": { + "none": "அறிவிப்புகள் இல்லை.", + "new_follower": "உங்களைப் பின்தொடர்ந்தார்!" + }, + "new_post": { + "new_post_text": "புதிய இடுகை", + "text_hint": "ஒரு இடுகையை உருவாக்க இங்கே தட்டவும்.", + "swearing": "இடுகையில் வெளிப்படையான மொழியைக் கொண்டிருக்க முடியாது.", + "post_to": "இடுகை இதற்கு" + }, + "messages": { + "coming_soon": "செய்திகள் இன்னும் தயாராக இல்லை. விரைவில் சரிபார்க்கவும்!" + }, + "language": "தமிழ்" +} diff --git a/apps/juxtaposition-ui/src/translations/uk.json b/apps/juxtaposition-ui/src/translations/uk.json index cd34419a..f2635a6f 100644 --- a/apps/juxtaposition-ui/src/translations/uk.json +++ b/apps/juxtaposition-ui/src/translations/uk.json @@ -12,7 +12,7 @@ "activity_feed": "Стрічка Активності", "notifications": "Сповіщення", "go_back": "Повернутися", - "yeahs": "Погодження", + "yeahs": "Так", "next": "Наступне", "exit": "Вихід" }, @@ -27,9 +27,9 @@ }, "community": { "follow": "Підписатися на спільноту", - "following": "Підписан", - "tags": "Теги", - "followers": "Підписчики", + "following": "Підписані", + "tags": "Теґи", + "followers": "Підписники", "popular": "Популярні Пости", "new": "Новий пост", "verified": "Перевірені Пости", @@ -40,16 +40,16 @@ "birthday": "День народження", "game_experience": "Ігровий Досвід", "follow_user": "Підписатися", - "following_user": "Підписан", + "following_user": "Підписані", "country": "Країна", "unfriend": "Вилучити з друзів", "posts": "Пости", "friends": "Друзі", - "following": "Підписан", + "following": "Підписані", "befriend": "Дружити", "pending": "В очікуванні", "no_friends": "Немає Друзів", - "no_following": "Ні На Кого Не Підписаний", + "no_following": "Ні на кого не Підписаний", "no_followers": "Немає Підписників", "followers": "Підписчики" }, @@ -91,16 +91,16 @@ "info": "Що таке Juxtaposition?", "welcome": "Ласкаво просимо до Juxtaposition!", "welcome_text": "Juxt — це ігрова спільнота, яка об’єднує людей з усього світу за допомогою персонажів Mii. Використовуйте Juxt, щоб ділитися своїм ігровим досвідом і зустрічатися з людьми з усього світу.", - "info_text": "Juxt — це ігрова спільнота, орієнтована на користувачів, де ви можете спілкуватися з людьми\n по всьому світу. Ви можете писати чи малювати пости в ігрових спільнотах або надсилати повідомлення безпосередньо друзям.", + "info_text": "Juxt — це ігрова спільнота, орієнтована на користувачів, де ви можете спілкуватися з людьми\n по всьому світу. Ви можете писати чи малювати пости в ігрових спільнотах або надсилати повідомлення безпосередньо друзям.", "experience": "Ігровий Досвід", "google": "Google Analytics", "google_text": "Juxtaposition використовує Google Analytics, щоб відстежувати, як користувачі використовують наш сервіс. Зібрані дані включають, але не обмежуються, час відвідування, відвідані сторінки та час, проведений на сайті. Ці дані жодним чином не пов’язані з вами та не використовуються для реклами ні нами, ні Google.", - "guest_text": "Наразі ми не можемо підтвердити ваш обліковий запис, що, ймовірно, означає, що ви зараз входите в обліковий запис Nintendo Network. Ви все ще зможете переглядати Juxt, але не зможете Yeah! публікації або створюйте власні публікації, доки не ввійдете в обліковий запис Pretendo Network. Для отримання додаткової інформації відвідайте сервер Discord." + "guest_text": "Наразі ми не можемо підтвердити ваш обліковий запис, що, ймовірно, означає, що ви ввійшли за допомогою облікового запису Nintendo Network. Ви зможете переглядати Juxt, але не зможете реагувати на дописи або створювати власні дописи, доки не ввійдете в систему за допомогою облікового запису Pretendo Network. Докладнішу інформацію можна знайти на сервері Discord." }, "language": "Українська", "user_settings": { "show_country": "Показати Країну в Профілі", - "show_game": "Показувати Ігровий Опит в Профілі", + "show_game": "Показувати Ігровий Досвід в Профілі", "show_comment": "Показувати Коментарі до Профілю", "swearing": "Коментарі профілю не можуть містити нецензурну лексику.", "profile_settings": "Налаштування Профілю", diff --git a/apps/juxtaposition-ui/src/translations/zh.json b/apps/juxtaposition-ui/src/translations/zh.json index 26ea00a5..9454c22a 100644 --- a/apps/juxtaposition-ui/src/translations/zh.json +++ b/apps/juxtaposition-ui/src/translations/zh.json @@ -74,7 +74,7 @@ "new_post_text": "新建帖子", "post_to": "发布到", "text_hint": "点击此处发布帖子。", - "swearing": "帖子不能包含明确的语言。" + "swearing": "请勿在帖子中包含露骨语言。" }, "messages": { "coming_soon": "消息尚未准备好。稍后检查!" diff --git a/apps/juxtaposition-ui/src/translations/zh_Hant.json b/apps/juxtaposition-ui/src/translations/zh_Hant.json index 804b6bb4..ee8c46d5 100644 --- a/apps/juxtaposition-ui/src/translations/zh_Hant.json +++ b/apps/juxtaposition-ui/src/translations/zh_Hant.json @@ -1,20 +1,126 @@ { - "language": "中文", + "language": "繁體中文", "global": { - "user_page": "用戶頁面", - "exit": "退出", - "activity_feed": "活動提要", - "messages": "通知", - "notifications": "通知", - "communities": "社區", - "go_back": "返回", - "yeahs": "同意", + "user_page": "個人檔案", + "exit": "離開", + "activity_feed": "動態消息", + "messages": "聊天訊息", + "notifications": "通知中心", + "communities": "社群", + "go_back": "返回上層", + "yeahs": "我贊同", "back": "返回", - "more": "加載更多帖子", + "more": "載入更多投稿", "private": "私人", "next": "繼續", - "no_posts": "沒有帖子", + "no_posts": "查無投稿", "close": "關閉", - "save": "保存" + "save": "儲存" + }, + "all_communities": { + "text": "所有社群", + "announcements": "公告", + "ann_string": "點選此處查看近期公告!", + "popular_places": "熱門社群", + "new_communities": "新設社群", + "show_all": "顯示所有", + "search": "搜尋社群……" + }, + "activity_feed": { + "empty": "這裡空空如也。先去跟隨其他人吧!" + }, + "messages": { + "coming_soon": "聊天功能尚未準備就緒。敬請期待!" + }, + "setup": { + "welcome": "歡迎來到 Juxtaposition!", + "beta": "《Beta 免責聲明》", + "beta_text": { + "first": "您即將使用之 Juxt 首個公開測試 Beta 版本,即知曉許多功能仍未開發完全,且可能隨時變動。", + "second": "您應知曉 Beta 期間之資料庫數據,日後可能遭到擦除。", + "third": "本網站及其軟體與附屬內容,均基於「現況性」與「可提供性」為開展。Pretendo Network 絕不保證(無論其明示抑或暗示)本網站及其軟體與附屬內容之合用性或可用性。" + }, + "info": "什麼是 Juxtaposition?", + "info_text": "Juxt 是由玩家們自發組成的遊戲社群;是能與全世界的人們相互交流的好所在。\n 您可以在遊戲社群投稿您的書寫文章以及繪畫作品,或者傳送訊息給好友。", + "rules": "《Juxt 社群守則》", + "rules_text": { + "first": "為了讓 Juxt 的大家都能享受樂趣,敬請詳閱《Juxt 社群守則》。以下將羅列數條為務必遵守的規範。", + "second": "投稿應全球皆宜", + "fourth": "人們應彼此共好", + "fifth": "為了讓 Juxt 的大家享受到樂趣,請您多對其他使用者關愛與包容。不投稿不適當或冒犯性的內容,和我們一同保持 Juxt 的優質氛圍。", + "sixth": "切勿投稿您或他人之個人資料", + "eighth": "切勿投稿劇透內容", + "tenth": "違反《守則》懲處", + "eleventh": "我們的目標是讓 Juxt 的大家都能享受樂趣。如果有人違反《Juxt 社群守則》,我們會採取適當行動,封鎖違規使用者或整臺遊戲機。", + "twelfth": "玩過遊戲了嗎?", + "thirteenth": "如果您在有玩過的遊戲上投稿,其內文將會附有一個圖示表明您曾經玩過。", + "third": "Juxt 匯聚了全世界人們對諸多遊戲的觀點。請您謹記,在遊戲社群投稿時既要真誠表達自己,也要考慮到他人感受,因為所有人都能看見您的投稿。投稿前應三思而後行。Juxt 服務範圍也包括網際網路,即使是不使用 Juxt 的人也能看見您的投稿。此外,您在好友投稿上的回應不僅是好友能看見,而是所有人。敬請留意。", + "seventh": "請您謹記,從 Juxt 認識某人不代表也從現實生活認識了他。切勿在 Juxt 共享您或他人的個人資料,包括電子郵件地址、工作場域、學校名稱等等。如果您在 Juxt 上收到某個人的線下見面邀請,請不要接受。Juxt 只是一個線上社群平臺,不應用於安排線下聚會。", + "ninth": "有些人來到 Juxt 找尋遊戲的技巧和訣竅,但也有些人只想自己發掘遊戲裡的各種隱藏要素。將遊戲隱藏要素或劇情透露的投稿,稱作\"劇透。\" 如果要投稿劇透內容,請確保該文已被勾選為劇透投稿。如此一來,不想被劇透的人就不會看到您的投稿。" + }, + "experience": "遊戲經驗", + "experience_text": { + "info": "選擇您自認對遊戲的上手程度。您可以在之後更改此設定。", + "beginner": "初來乍到", + "intermediate": "休閒業餘", + "expert": "技藝高超" + }, + "welcome_text": "Juxt 旨在讓世界各地的人們以 Mii 化身串聯遊戲社群。使用 Juxt,向全世界分享您獨到的遊戲經歷。", + "guest": "訪客模式", + "google": "Google Analytics(分析)", + "ready": "開始使用 Juxt", + "done": "祝您在 Juxt 玩得愉快!", + "done_button": "走吧!", + "guest_button": "我明白了", + "google_text": "Juxtaposition 使用 Google Analytics(分析)來追蹤使用者存取服務的情況。收集的資料包括但不限於拜訪時間、拜訪的頁面以及停留頁面的時間。這些資料不會以任何方式與您關連,也不會被我們或 Google 用於分析廣告投放。", + "ready_text": "先到社群頁面,觀摩來自全世界的投稿作品吧。還能趁機熟悉 Juxt 的介面。您也可能就此發現新世界!", + "guest_text": "目前無法驗證您的帳號(可能是因為您使用了 Nintendo Network 帳號)。您可以繼續瀏覽 Juxt,但在登入 Pretendo Network 前,將無法進行「我贊同!」或投稿內容。詳情請前往 Discord 伺服器瞭解。" + }, + "community": { + "recent": "近期投稿", + "popular": "熱門投稿", + "verified": "名人投稿", + "new": "投稿", + "follow": "跟隨社群", + "following": "跟隨中", + "followers": "跟隨者", + "posts": "投稿", + "tags": "標籤" + }, + "user_page": { + "game_experience": "遊戲經驗", + "posts": "投稿", + "friends": "好友", + "following": "跟隨中", + "followers": "跟隨者", + "follow_user": "跟隨", + "befriend": "成為好友", + "pending": "待回應", + "unfriend": "取消好友", + "no_friends": "查無好友", + "country": "居住地", + "birthday": "生日", + "following_user": "跟隨中", + "no_following": "查無跟隨了哪些人", + "no_followers": "查無跟隨者" + }, + "user_settings": { + "show_country": "在個人檔案顯示居住地", + "show_birthday": "在個人檔案顯示生日", + "show_game": "在個人檔案顯示遊戲經驗", + "show_comment": "在個人檔案顯示自我介紹", + "profile_comment": "自我介紹", + "profile_settings": "個人檔案設定", + "swearing": "自我介紹不得包含露骨內容。" + }, + "notifications": { + "none": "查無通知。", + "new_follower": "成為了您的跟隨者!" + }, + "new_post": { + "swearing": "投稿不得包含露骨內容。", + "new_post_text": "新投稿", + "post_to": "投稿至", + "text_hint": "點選此處進行投稿。" } } diff --git a/apps/juxtaposition-ui/src/types/common/param-pack.ts b/apps/juxtaposition-ui/src/types/common/param-pack.ts new file mode 100644 index 00000000..bf28b254 --- /dev/null +++ b/apps/juxtaposition-ui/src/types/common/param-pack.ts @@ -0,0 +1,16 @@ +export interface ParamPack { + title_id: string; + access_key: string; + platform_id: string; + region_id: string; + language_id: string; + country_id: string; + area_id: string; + network_restriction: string; + friend_restriction: string; + rating_restriction: string; + rating_organization: string; + transferable_id: string; + tz_name: string; + utc_offset: string; +} diff --git a/apps/miiverse-api/src/types/common/token.ts b/apps/juxtaposition-ui/src/types/common/service-token.ts similarity index 100% rename from apps/miiverse-api/src/types/common/token.ts rename to apps/juxtaposition-ui/src/types/common/service-token.ts diff --git a/apps/juxtaposition-ui/src/types/common/system-types.ts b/apps/juxtaposition-ui/src/types/common/system-types.ts new file mode 100644 index 00000000..1edbac75 --- /dev/null +++ b/apps/juxtaposition-ui/src/types/common/system-types.ts @@ -0,0 +1,6 @@ +export enum SystemType { + WUP = 1, + CTR = 2, + API = 3, + PasswordReset = 0xFF // * This kinda blows +} diff --git a/apps/juxtaposition-ui/src/types/common/token-types.ts b/apps/juxtaposition-ui/src/types/common/token-types.ts new file mode 100644 index 00000000..241372c2 --- /dev/null +++ b/apps/juxtaposition-ui/src/types/common/token-types.ts @@ -0,0 +1,7 @@ +export enum TokenType { + OAuthAccess = 1, + OAuthRefresh = 2, + NEX = 3, + IndependentService = 4, + PasswordReset = 5 +} diff --git a/apps/juxtaposition-ui/src/types/juxt/notification.ts b/apps/juxtaposition-ui/src/types/juxt/notification.ts new file mode 100644 index 00000000..93e0e1ea --- /dev/null +++ b/apps/juxtaposition-ui/src/types/juxt/notification.ts @@ -0,0 +1,9 @@ +export type Notification = { + pid: number; + type: 'notice' | 'yeah' | 'reply' | 'follow'; + text: string; + image: string; + link: string; + objectID: string; + userPID: number; +}; diff --git a/apps/juxtaposition-ui/src/util.js b/apps/juxtaposition-ui/src/util.js deleted file mode 100644 index 75c59459..00000000 --- a/apps/juxtaposition-ui/src/util.js +++ /dev/null @@ -1,567 +0,0 @@ -const crypto = require('crypto'); -const grpc = require('nice-grpc'); -const { AccountDefinition } = require('@pretendonetwork/grpc/account/account_service'); -const { FriendsDefinition } = require('@pretendonetwork/grpc/friends/friends_service'); -const { APIDefinition } = require('@pretendonetwork/grpc/api/api_service'); -const HashMap = require('hashmap'); -const TGA = require('tga'); -const imagePixels = require('image-pixels'); -const pako = require('pako'); -const PNG = require('pngjs').PNG; -const bmp = require('bmp-js'); -const sharp = require('sharp'); -const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); -const crc32 = require('crc/crc32'); -const translations = require('./translations'); -const database = require('@/database'); -const { COMMUNITY } = require('@/models/communities'); -const { NOTIFICATION } = require('@/models/notifications'); -const { logger } = require('@/logger'); -const { CONTENT } = require('@/models/content'); -const { SETTINGS } = require('@/models/settings'); -const { LOGS } = require('@/models/logs'); -const { config } = require('@/config'); -const communityMap = new HashMap(); -const userMap = new HashMap(); - -const { host: friendsIP, port: friendsPort, apiKey: friendsKey } = config.grpc.friends; -const friendsChannel = grpc.createChannel(`${friendsIP}:${friendsPort}`); -const friendsClient = grpc.createClient(FriendsDefinition, friendsChannel); - -const { host: apiIP, port: apiPort, apiKey: apiKey } = config.grpc.account; -const apiChannel = grpc.createChannel(`${apiIP}:${apiPort}`); -const apiClient = grpc.createClient(APIDefinition, apiChannel); - -const accountChannel = grpc.createChannel(`${apiIP}:${apiPort}`); -const accountClient = grpc.createClient(AccountDefinition, accountChannel); - -const s3 = new S3Client({ - endpoint: config.s3.endpoint, - forcePathStyle: true, - region: config.s3.region, - credentials: { - accessKeyId: config.s3.key, - secretAccessKey: config.s3.secret - } -}); - -nameCache(); - -function nameCache() { - database.connect().then(async () => { - const communities = await COMMUNITY.find(); - if (communities !== null) { - for (let i = 0; i < communities.length; i++) { - if (communities[i].title_id !== null) { - for (let j = 0; j < communities[i].title_id.length; j++) { - communityMap.set(communities[i].title_id[j], communities[i].name); - communityMap.set(communities[i].title_id[j] + '-id', communities[i].olive_community_id); - } - communityMap.set(communities[i].olive_community_id, communities[i].name); - } - } - logger.success('Created community index of ' + communities.length + ' communities'); - } - const users = await database.getUsersSettings(-1); - if (users !== null) { - for (let i = 0; i < users.length; i++) { - if (users[i].pid !== null) { - userMap.set(users[i].pid, users[i].screen_name.replace(/[\u{0080}-\u{FFFF}]/gu, '').replace(/\u202e/g, '')); - } - } - logger.success('Created user index of ' + users.length + ' users'); - } - }).catch((error) => { - logger.error(error); - }); -} - -// TODO - This doesn't belong here, just hacking it in. Gonna redo this whole server anyway so fuck it -const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu; -async function create_user(pid, experience, notifications) { - const pnid = await this.getUserDataFromPid(pid); - if (!pnid) { - return; - } - const newSettings = { - pid: pid, - screen_name: pnid.mii.name, - game_skill: experience, - receive_notifications: notifications - }; - const newContent = { - pid: pid - }; - const newSettingsObj = new SETTINGS(newSettings); - await newSettingsObj.save(); - - const newContentObj = new CONTENT(newContent); - await newContentObj.save(); - - this.setName(pid, pnid.mii.name); -} - -/** - * Decodes and converts a Nintendo param pack to a JavaScript object. - * @param {string} paramPack base64-encoded param pack - * @returns {Record} - */ -function decodeParamPack(paramPack) { - /* Decode base64 */ - let dec = Buffer.from(paramPack, 'base64').toString('ascii'); - /* Remove starting and ending '/', split into array */ - dec = dec.slice(1, -1).split('\\'); - /* Parameters are in the format [name, val, name, val]. Copy into out{}. */ - const out = {}; - for (let i = 0; i < dec.length; i += 2) { - out[dec[i].trim()] = dec[i + 1].trim(); - } - return out; -} -function processServiceToken(encryptedToken) { - try { - const B64token = Buffer.from(encryptedToken, 'base64'); - const decryptedToken = this.decryptToken(B64token); - const token = this.unpackServiceToken(decryptedToken); - - // * Only allow token types 1 (Wii U) and 2 (3DS) - if (token.system_type !== 1 && token.system_type !== 2) { - return null; - } - - // * Check if the token is expired - if (token.issue_time + (24n * 3600n * 1000n) < Date.now()) { - return null; - } - - return token.pid; - } catch (e) { - logger.error(e, 'Could not process service token'); - return null; - } -} -function decryptToken(token) { - if (!config.aesKey) { - throw new Error('Service token AES key not found. Set config.aesKey'); - } - - const iv = Buffer.alloc(16); - const key = Buffer.from(config.aesKey, 'hex'); - - const expectedChecksum = token.readUint32BE(); - const encryptedBody = token.subarray(4); - - const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); - - const decrypted = Buffer.concat([ - decipher.update(encryptedBody), - decipher.final() - ]); - - if (expectedChecksum !== crc32(decrypted)) { - throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?'); - } - - return decrypted; -} -function unpackServiceToken(token) { - return { - system_type: token.readUInt8(0x0), - token_type: token.readUInt8(0x1), - pid: token.readUInt32LE(0x2), - issue_time: token.readBigUInt64LE(0x6), - title_id: token.readBigUInt64LE(0xE), - access_level: token.readInt8(0x16) - }; -} -async function processPainting(painting, isTGA) { - if (isTGA) { - const paintingBuffer = Buffer.from(painting, 'base64'); - let output = ''; - try { - output = pako.inflate(paintingBuffer); - } catch (err) { - logger.error(err, 'Could not decompress painting'); - return null; - } - let tga; - try { - tga = new TGA(Buffer.from(output)); - } catch (e) { - logger.error(e, 'Could not parse painting'); - return null; - } - const png = new PNG({ - width: tga.width, - height: tga.height - }); - png.data = tga.pixels; - return PNG.sync.write(png); - // return `data:image/png;base64,${pngBuffer.toString('base64')}`; - } else { - const paintingBuffer = Buffer.from(painting, 'base64'); - const bitmap = bmp.decode(paintingBuffer); - const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false); - - let output; - try { - output = pako.deflate(tga, { level: 6 }); - } catch (err) { - logger.error(err, 'Could not compress painting'); - return null; - } - return new Buffer(output).toString('base64'); - } -} -function nintendoPasswordHash(password, pid) { - const pidBuffer = Buffer.alloc(4); - pidBuffer.writeUInt32LE(pid); - - const unpacked = Buffer.concat([ - pidBuffer, - Buffer.from('\x02\x65\x43\x46'), - Buffer.from(password) - ]); - return crypto.createHash('sha256').update(unpacked).digest().toString('hex'); -} -function getCommunityHash() { - return communityMap; -} -function getUserHash() { - return userMap; -} -function refreshCache() { - nameCache(); -} -function setName(pid, name) { - if (!pid || !name) { - return; - } - userMap.delete(pid); - userMap.set(pid, name.replace(/[\u{0080}-\u{FFFF}]/gu, '').replace(/\u202e/g, '')); -} - -function updateCommunityHash(community) { - if (!community) { - return; - } - for (let i = 0; i < community.title_id.length; i++) { - communityMap.set(community.title_id[i], community.name); - communityMap.set(community.title_id[i] + '-id', community.olive_community_id); - } - communityMap.set(community.olive_community_id, community.name); -} - -function getReasonMap() { - return [ - 'Spoiler', - 'Personal Information', - 'Violent Content', - 'Inappropriate/Harmful Conduct', - 'Hateful/Bullying', - 'Advertising', - 'Sexually Explicit', - 'Piracy', - 'Inappropriate Behavior in Game', - 'Other', - 'Missing Images; Reach out to Jemma with post link to fix' - ]; -} - -async function resizeImage(file, width, height) { - return new Promise(function (resolve) { - const image = Buffer.from(file, 'base64'); - sharp(image) - .resize({ height: height, width: width }) - .toBuffer() - .then((data) => { - resolve(data); - }).catch(err => logger.error(err, 'Could not resize image')); - }); -} - -async function getTGAFromPNG(image) { - const pngData = await imagePixels(Buffer.from(image)); - const tga = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); - let output; - try { - output = pako.deflate(tga, { level: 6 }); - } catch (err) { - logger.error(err, 'Could not decompress image'); - return null; - } - return new Buffer(output).toString('base64').trim(); -} - -function createBMPTgaBuffer(width, height, pixels, dontFlipY) { - const buffer = Buffer.alloc(18 + pixels.length); - // write header - buffer.writeInt8(0, 0); - buffer.writeInt8(0, 1); - buffer.writeInt8(2, 2); - buffer.writeInt16LE(0, 3); - buffer.writeInt16LE(0, 5); - buffer.writeInt8(0, 7); - buffer.writeInt16LE(0, 8); - buffer.writeInt16LE(0, 10); - buffer.writeInt16LE(width, 12); - buffer.writeInt16LE(height, 14); - buffer.writeInt8(32, 16); - buffer.writeInt8(8, 17); - - let offset = 18; - for (let i = 0; i < height; i++) { - for (let j = 0; j < width; j++) { - const idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4; - buffer.writeUInt8(pixels[idx + 1], offset++); // b - buffer.writeUInt8(pixels[idx + 2], offset++); // g - buffer.writeUInt8(pixels[idx + 3], offset++); // r - buffer.writeUInt8(255, offset++); // a - } - } - - return buffer; -} -function processLanguage(paramPackData) { - if (!paramPackData) { - return translations.EN; - } - switch (paramPackData.language_id) { - case '0': - return translations.JA; - case '1': - return translations.EN; - case '2': - return translations.FR; - case '3': - return translations.DE; - case '4': - return translations.IT; - case '5': - return translations.ES; - case '6': - return translations.ZH; - case '7': - return translations.KO; - case '8': - return translations.NL; - case '9': - return translations.PT; - case '10': - return translations.RU; - case '11': - return translations.ZH; - default: - return translations.EN; - } -} -async function uploadCDNAsset(key, data, acl) { - const awsPutParams = new PutObjectCommand({ - Body: data, - Key: key, - Bucket: config.s3.bucket, - ACL: acl - }); - try { - await s3.send(awsPutParams); - return true; - } catch (e) { - logger.error(e, 'Could not upload to CDN'); - return false; - } -} -async function newNotification(notification) { - const now = new Date(); - if (notification.type === 'follow') { - // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } - let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID }); - if (existingNotification) { - existingNotification.lastUpdated = now; - existingNotification.read = false; - return await existingNotification.save(); - } - const last60min = new Date(now.getTime() - 60 * 60 * 1000); - existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last60min } }); - if (existingNotification) { - existingNotification.users.push({ - user: notification.objectID, - timeStamp: now - }); - existingNotification.lastUpdated = now; - existingNotification.link = notification.link; - existingNotification.objectID = notification.objectID; - existingNotification.read = false; - return await existingNotification.save(); - } else { - const newNotification = new NOTIFICATION({ - pid: notification.pid, - type: notification.type, - users: [{ - user: notification.objectID, - timestamp: now - }], - link: notification.link, - objectID: notification.objectID, - read: false, - lastUpdated: now - }); - await newNotification.save(); - } - } else if (notification.type == 'notice') { - const newNotification = new NOTIFICATION({ - pid: notification.pid, - type: notification.type, - text: notification.text, - image: notification.image, - link: notification.link, - read: false, - lastUpdated: now - }); - await newNotification.save(); - } - /* else if(notification.type === 'yeah') { - // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } - let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID }) - if(existingNotification) { - existingNotification.lastUpdated = new Date(); - return await existingNotification.save(); - } - existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' }); - if(existingNotification) { - existingNotification.users.push({ - user: notification.objectID, - timeStamp: new Date() - }); - existingNotification.lastUpdated = new Date(); - existingNotification.link = notification.link; - existingNotification.objectID = notification.objectID; - return await existingNotification.save(); - } - else { - let newNotification = new NOTIFICATION({ - pid: notification.pid, - type: notification.type, - users: [{ - user: notification.objectID, - timestamp: new Date() - }], - link: notification.link, - objectID: notification.objectID, - read: false, - lastUpdated: new Date() - }); - await newNotification.save(); - } - } */ -} -async function getFriends(pid) { - try { - const pids = await friendsClient.getUserFriendPIDs({ - pid: pid - }, { - metadata: grpc.Metadata({ - 'X-API-Key': friendsKey - }) - }); - return pids.pids; - } catch (e) { - logger.error(e, `Failed to get friends for ${pid}`); - return []; - } -} -async function getFriendRequests(pid) { - try { - const requests = await friendsClient.getUserFriendRequestsIncoming({ - pid: pid - }, { - metadata: grpc.Metadata({ - 'X-API-Key': friendsKey - }) - }); - return requests.friendRequests; - } catch (e) { - logger.error(e, `Failed to get friend requests for ${pid}`); - return []; - } -} -async function login(username, password) { - return await apiClient.login({ - username: username, - password: password, - grantType: 'password' - }, { - metadata: grpc.Metadata({ - 'X-API-Key': apiKey - }) - }); -} -async function refreshLogin(refreshToken) { - return await apiClient.login({ - refreshToken: refreshToken - }, { - metadata: grpc.Metadata({ - 'X-API-Key': apiKey - }) - }); -} -async function getUserDataFromToken(token) { - return apiClient.getUserData({}, { - metadata: grpc.Metadata({ - 'X-API-Key': apiKey, - 'X-Token': token - }) - }); -} -async function getUserDataFromPid(pid) { - return accountClient.getUserData({ - pid: pid - }, { - metadata: grpc.Metadata({ - 'X-API-Key': apiKey - }) - }); -} -async function getPid(token) { - const user = await this.getUserDataFromToken(token); - return user.pid; -} -async function createLogEntry(actor, action, target, context, fields) { - const newLog = new LOGS({ - actor: actor, - action: action, - target: target, - context: context, - changed_fields: fields - }); - await newLog.save(); -} -module.exports = { - decodeParamPack, - processServiceToken, - decryptToken, - unpackServiceToken, - processPainting, - nintendoPasswordHash, - getCommunityHash, - getUserHash, - refreshCache, - setName, - updateCommunityHash, - getReasonMap, - resizeImage, - getTGAFromPNG, - createBMPTgaBuffer, - processLanguage, - uploadCDNAsset, - newNotification, - getFriends, - getFriendRequests, - login, - refreshLogin, - getUserDataFromToken, - getUserDataFromPid, - getPid, - create_user, - INVALID_POST_BODY_REGEX, - createLogEntry -}; diff --git a/apps/juxtaposition-ui/src/util.ts b/apps/juxtaposition-ui/src/util.ts new file mode 100644 index 00000000..92ec8e19 --- /dev/null +++ b/apps/juxtaposition-ui/src/util.ts @@ -0,0 +1,463 @@ +import crypto from 'crypto'; +import { createChannel, createClient, Metadata } from 'nice-grpc'; +import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service'; +import { FriendsDefinition } from '@pretendonetwork/grpc/friends/friends_service'; +import { APIDefinition } from '@pretendonetwork/grpc/api/api_service'; +import HashMap from 'hashmap'; +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +import crc32 from 'crc/crc32'; +import database from '@/database'; +import { COMMUNITY } from '@/models/communities'; +import { NOTIFICATION } from '@/models/notifications'; +import { logger } from '@/logger'; +import { CONTENT } from '@/models/content'; +import { SETTINGS } from '@/models/settings'; +import { LOGS } from '@/models/logs'; +import { config } from '@/config'; +import { SystemType } from '@/types/common/system-types'; +import { TokenType } from '@/types/common/token-types'; +import translations from './translations'; +import type { ObjectCannedACL } from '@aws-sdk/client-s3'; +import type { NotificationSchema } from '@/models/notifications'; +import type { CommunitySchema } from '@/models/communities'; +import type { ParamPack } from '@/types/common/param-pack'; +import type { ServiceToken } from '@/types/common/service-token'; +import type { InferSchemaType } from 'mongoose'; +import type { Notification } from '@/types/juxt/notification'; +import type { GetUserDataResponse as AccountGetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { GetUserDataResponse as ApiGetUserDataResponse } from '@pretendonetwork/grpc/api/get_user_data_rpc'; +import type { FriendRequest } from '@pretendonetwork/grpc/friends/friend_request'; +import type { LoginResponse } from '@pretendonetwork/grpc/api/login_rpc'; + +const gRPCFriendsChannel = createChannel(`${config.grpc.friends.host}:${config.grpc.friends.port}`); +const gRPCFriendsClient = createClient(FriendsDefinition, gRPCFriendsChannel); + +const gRPCAccountChannel = createChannel(`${config.grpc.account.host}:${config.grpc.account.port}`); +const gRPCAccountClient = createClient(AccountDefinition, gRPCAccountChannel); + +const gRPCApiChannel = createChannel(`${config.grpc.account.host}:${config.grpc.account.port}`); +const gRPCApiClient = createClient(APIDefinition, gRPCApiChannel); + +const s3 = new S3Client({ + endpoint: config.s3.endpoint, + forcePathStyle: true, + region: config.s3.region, + credentials: { + accessKeyId: config.s3.key, + secretAccessKey: config.s3.secret + } +}); + +const communityMap = new HashMap(); +const userMap = new HashMap(); + +/** + * Map from {olive_community_id, title_id} to name + * and also title_id + '-id' to olive_community_id + */ +export function getCommunityHash(): HashMap { + return communityMap; +} +/** + * Map from pid to screen_name (transformed) + */ +export function getUserHash(): HashMap { + return userMap; +} + +refreshCache(); + +function refreshCache(): void { + database.connect().then(async () => { + for await (const community of COMMUNITY.find()) { + updateCommunityHash(community); + } + logger.success('Created community index'); + + const users = await database.getUsersSettings(-1); + + for (const user of users) { + if (user.pid === undefined || user.screen_name === undefined) { + continue; + } + + setName(user.pid, user.screen_name); + } + logger.success('Created user index of ' + users.length + ' users'); + }).catch((error) => { + logger.error(error); + }); +} + +/** + * Updates a user's name in the user map. + */ +export function setName(pid: number, name: string): void { + userMap.set(pid, name.replace(/[\u{0080}-\u{FFFF}]/gu, '').replace(/\u202e/g, '')); +} + +/** + * Updates a community's info in the map. + */ +export function updateCommunityHash(community: InferSchemaType): void { + if (community.title_id === undefined || + community.olive_community_id === undefined || + community.name === undefined + ) { + return; + } + + for (const title_id of community.title_id) { + communityMap.set(title_id, community.name); + communityMap.set(title_id + '-id', community.olive_community_id); + } + communityMap.set(community.olive_community_id, community.name); +} + +// TODO - This doesn't belong here, just hacking it in. Gonna redo this whole server anyway so fuck it +export const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu; + +export async function createUser(pid: number, experience: number, notifications: boolean): Promise { + const pnid = await getUserAccountData(pid); + if (!pnid) { + return; + } + + const name = pnid.mii?.name ?? 'Default'; + + const newSettings = { + pid: pid, + screen_name: name, + game_skill: experience, + receive_notifications: notifications + }; + const newContent = { + pid: pid + }; + const newSettingsObj = new SETTINGS(newSettings); + await newSettingsObj.save(); + + const newContentObj = new CONTENT(newContent); + await newContentObj.save(); + + setName(pid, name); +} + +export function decodeParamPack(paramPack: string): ParamPack { + const values = Buffer.from(paramPack, 'base64').toString().split('\\').filter(v => v.length > 0).values(); + const entries: Record = {}; + for (let i = 0; i < 16; i++) { /* Enforce an upper limit on ParamPack decoding */ + // Keys and values are sibling list entries + const paramKey = values.next().value; + const paramVal = values.next().value; + // We hit the end of the list + if (paramKey === undefined || paramVal === undefined) { + break; + } + + entries[paramKey] = paramVal; + } + + // normalize and prevent any funny businiess from clients + // one day this can be a proper DTO + return { + title_id: entries.title_id ?? '', + access_key: entries.access_key ?? '', + platform_id: entries.platform_id ?? '', + region_id: entries.region_id ?? '', + language_id: entries.language_id ?? '', + country_id: entries.country_id ?? '', + area_id: entries.area_id ?? '', + network_restriction: entries.network_restriction ?? '', + friend_restriction: entries.friend_restriction ?? '', + rating_restriction: entries.rating_restriction ?? '', + rating_organization: entries.rating_organization ?? '', + transferable_id: entries.transferable_id ?? '', + tz_name: entries.tz_name ?? '', + utc_offset: entries.utc_offset ?? '' + }; +} + +export function getPIDFromServiceToken(token: string): number | null { + try { + const decryptedToken = decryptToken(Buffer.from(token, 'base64')); + + if (!decryptedToken) { + return null; + } + + const unpackedToken = unpackServiceToken(decryptedToken); + if (unpackedToken === null) { + return null; + } + + // * Only allow token types 1 (Wii U) and 2 (3DS) + if (unpackedToken.system_type !== SystemType.CTR && unpackedToken.system_type !== SystemType.WUP) { + return null; + } + + // * Check if the token is expired + if (unpackedToken.issue_time + (24n * 3600n * 1000n) < Date.now()) { + return null; + } + + return unpackedToken.pid; + } catch (e) { + logger.error(e, 'Failed to extract PID from service token'); + return null; + } +} + +function decryptToken(token: Buffer): Buffer { + const iv = Buffer.alloc(16); + + const expectedChecksum = token.readUint32BE(); + const encryptedBody = token.subarray(4); + + const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aesKey, 'hex'), iv); + + const decrypted = Buffer.concat([ + decipher.update(encryptedBody), + decipher.final() + ]); + + if (expectedChecksum !== crc32(decrypted)) { + throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?'); + } + + return decrypted; +} + +export function unpackServiceToken(token: Buffer): ServiceToken | null { + const token_type = token.readUInt8(0x1); + if (token_type !== TokenType.IndependentService) { + return null; + } + + return { + system_type: token.readUInt8(0x0), + token_type: token.readUInt8(0x1), + pid: token.readUInt32LE(0x2), + issue_time: token.readBigUInt64LE(0x6), + title_id: token.readBigUInt64LE(0xE), + access_level: token.readInt8(0x16) + }; +} + +export function getReasonMap(): string[] { + return [ + 'Spoiler', + 'Personal Information', + 'Violent Content', + 'Inappropriate/Harmful Conduct', + 'Hateful/Bullying', + 'Advertising', + 'Sexually Explicit', + 'Piracy', + 'Inappropriate Behavior in Game', + 'Other', + 'Missing Images; Reach out to Jemma with post link to fix' + ]; +} + +export function processLanguage(paramPack?: ParamPack): typeof translations.EN { + if (!paramPack) { + return translations.EN; + } + const languageIdMap: Record = { + 0: 'JA', + 1: 'EN', + 2: 'FR', + 3: 'DE', + 4: 'IT', + 5: 'ES', + 6: 'ZH', + 7: 'KO', + 8: 'NL', + 9: 'PT', + 10: 'RU', + 11: 'ZH' + }; + + const language = languageIdMap[paramPack.language_id] ?? 'EN'; + return translations[language]; +} + +export async function uploadCDNAsset(key: string, data: Buffer, acl: ObjectCannedACL): Promise { + const awsPutParams = new PutObjectCommand({ + Body: data, + Key: key, + Bucket: config.s3.bucket, + ACL: acl + }); + try { + await s3.send(awsPutParams); + return true; + } catch (e) { + logger.error(e, 'Could not upload to CDN'); + return false; + } +} + +export async function newNotification(notification: Notification): Promise | null> { + const now = new Date(); + if (notification.type === 'follow') { + // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } + let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID }); + if (existingNotification) { + existingNotification.lastUpdated = now; + existingNotification.read = false; + return existingNotification.save(); + } + const last60min = new Date(now.getTime() - 60 * 60 * 1000); + existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last60min } }); + if (existingNotification) { + existingNotification.users.push({ + user: notification.objectID, + timestamp: now + }); + existingNotification.lastUpdated = now; + existingNotification.link = notification.link; + existingNotification.objectID = notification.objectID; + existingNotification.read = false; + return existingNotification.save(); + } else { + const newNotification = new NOTIFICATION({ + pid: notification.pid, + type: notification.type, + users: [{ + user: notification.objectID, + timestamp: now + }], + link: notification.link, + objectID: notification.objectID, + read: false, + lastUpdated: now + }); + return newNotification.save(); + } + } else if (notification.type == 'notice') { + const newNotification = new NOTIFICATION({ + pid: notification.pid, + type: notification.type, + text: notification.text, + image: notification.image, + link: notification.link, + read: false, + lastUpdated: now + }); + return newNotification.save(); + } + /* else if(notification.type === 'yeah') { + // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } + let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID }) + if(existingNotification) { + existingNotification.lastUpdated = new Date(); + return await existingNotification.save(); + } + existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' }); + if(existingNotification) { + existingNotification.users.push({ + user: notification.objectID, + timeStamp: new Date() + }); + existingNotification.lastUpdated = new Date(); + existingNotification.link = notification.link; + existingNotification.objectID = notification.objectID; + return await existingNotification.save(); + } + else { + let newNotification = new NOTIFICATION({ + pid: notification.pid, + type: notification.type, + users: [{ + user: notification.objectID, + timestamp: new Date() + }], + link: notification.link, + objectID: notification.objectID, + read: false, + lastUpdated: new Date() + }); + await newNotification.save(); + } + } */ + return null; +} + +export async function getUserFriendPIDs(pid: number): Promise { + const response = await gRPCFriendsClient.getUserFriendPIDs({ + pid: pid + }, { + metadata: Metadata({ + 'X-API-Key': config.grpc.friends.apiKey + }) + }); + + return response.pids; +} + +export async function getUserFriendRequestsIncoming(pid: number): Promise { + const response = await gRPCFriendsClient.getUserFriendRequestsIncoming({ + pid: pid + }, { + metadata: Metadata({ + 'X-API-Key': config.grpc.friends.apiKey + }) + }); + + return response.friendRequests; +} + +export function getUserAccountData(pid: number): Promise { + return gRPCAccountClient.getUserData({ + pid: pid + }, { + metadata: Metadata({ + 'X-API-Key': config.grpc.account.apiKey + }) + }); +} + +export async function getUserDataFromToken(token: string): Promise { + return gRPCApiClient.getUserData({}, { + metadata: Metadata({ + 'X-API-Key': config.grpc.account.apiKey, + 'X-Token': token + }) + }); +} + +export async function login(username: string, password: string): Promise { + return await gRPCApiClient.login({ + username: username, + password: password, + grantType: 'password' + }, { + metadata: Metadata({ + 'X-API-Key': config.grpc.account.apiKey + }) + }); +} + +/* Unused until refresh token auth is implemented */ +// export async function refreshLogin(refreshToken: string): Promise { +// return await gRPCApiClient.login({ +// refreshToken: refreshToken +// }, { +// metadata: Metadata({ +// 'X-API-Key': config.grpc.account.apiKey +// }) +// }); +// } + +export async function createLogEntry(actor: number, action: string, target: string, context: string, fields: string[]): Promise { + const newLog = new LOGS({ + actor: actor, + action: action, + target: target, + context: context, + changed_fields: fields + }); + await newLog.save(); +} diff --git a/apps/juxtaposition-ui/src/webfiles/web/error.ejs b/apps/juxtaposition-ui/src/webfiles/web/error.ejs index 516e7086..d3d298dc 100644 --- a/apps/juxtaposition-ui/src/webfiles/web/error.ejs +++ b/apps/juxtaposition-ui/src/webfiles/web/error.ejs @@ -2,7 +2,7 @@ - Juxtaposition Log In + Juxt - Error @@ -67,7 +67,9 @@

<%= code %>: <%= message %>

-

View current server status here.

+

View current server status.

+

Or, go back to the homepage.

+

Request ID: <%= id %>

diff --git a/apps/juxtaposition-ui/src/webfiles/web/login.ejs b/apps/juxtaposition-ui/src/webfiles/web/login.ejs index 308dc0da..6e39cd70 100644 --- a/apps/juxtaposition-ui/src/webfiles/web/login.ejs +++ b/apps/juxtaposition-ui/src/webfiles/web/login.ejs @@ -83,6 +83,7 @@ Don't have an account? + diff --git a/apps/miiverse-api/package.json b/apps/miiverse-api/package.json index b8c414d9..9ce6a240 100644 --- a/apps/miiverse-api/package.json +++ b/apps/miiverse-api/package.json @@ -21,11 +21,13 @@ "colors": "^1.4.0", "crc": "^4.3.2", "express": "^4.17.1", + "express-async-errors": "^3.1.1", "express-prom-bundle": "^7.0.2", + "express-rate-limit": "^7.5.0", "express-subdomain": "^1.0.5", "fs-extra": "^9.0.0", "moment": "^2.24.0", - "mongoose": "^6.10.1", + "mongoose": "^8.15.1", "multer": "^1.4.5-lts.1", "nice-grpc": "^2.1.12", "node-snowflake": "0.0.1", diff --git a/apps/miiverse-api/src/database.ts b/apps/miiverse-api/src/database.ts index 565857c8..f0f56d32 100644 --- a/apps/miiverse-api/src/database.ts +++ b/apps/miiverse-api/src/database.ts @@ -11,7 +11,7 @@ import type { HydratedConversationDocument } from '@/types/mongoose/conversation import type { HydratedContentDocument } from '@/types/mongoose/content'; import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; import type { HydratedEndpointDocument } from '@/types/mongoose/endpoint'; -import type { HydratedPostDocument, IPost } from '@/types/mongoose/post'; +import type { HydratedPostDocument, IPostInput } from '@/types/mongoose/post'; import type { HydratedCommunityDocument } from '@/types/mongoose/community'; let connection: mongoose.Connection; @@ -97,7 +97,7 @@ export async function getPostReplies(postID: string, limit: number): Promise { +export async function getDuplicatePosts(pid: number, post: IPostInput): Promise { verifyConnected(); return Post.findOne({ diff --git a/apps/miiverse-api/src/errors.ts b/apps/miiverse-api/src/errors.ts index 72cb9db8..6b730f07 100644 --- a/apps/miiverse-api/src/errors.ts +++ b/apps/miiverse-api/src/errors.ts @@ -57,7 +57,7 @@ export enum ApiErrorCode { FAIL_POST_NOCOMMENT = 935 } -function sendError(response: express.Response, errorCode: ApiErrorCode, httpCode: number): void { +function sendXMLError(response: express.Response, errorCode: ApiErrorCode, httpCode: number): void { response.type('application/xml'); response.status(httpCode); // Save this for the logger @@ -75,9 +75,9 @@ function sendError(response: express.Response, errorCode: ApiErrorCode, httpCode } export function badRequest(response: express.Response, errorCode: number, httpCode: number = 400): void { - return sendError(response, errorCode, httpCode); + return sendXMLError(response, errorCode, httpCode); } export function serverError(response: express.Response, errorCode: ApiErrorCode, httpCode: number = 500): void { - return sendError(response, errorCode, httpCode); + return sendXMLError(response, errorCode, httpCode); } diff --git a/apps/miiverse-api/src/middleware/auth.ts b/apps/miiverse-api/src/middleware/auth.ts index dd8a67f7..5cda8b68 100644 --- a/apps/miiverse-api/src/middleware/auth.ts +++ b/apps/miiverse-api/src/middleware/auth.ts @@ -43,8 +43,8 @@ async function auth(request: express.Request, response: express.Response, next: return badRequest(response, ApiErrorCode.NO_TOKEN); } - const pid: number = getPIDFromServiceToken(encryptedToken); - if (pid === 0) { + const pid = getPIDFromServiceToken(encryptedToken); + if (pid === null) { return badRequest(response, ApiErrorCode.BAD_TOKEN); } diff --git a/apps/miiverse-api/src/models/post.ts b/apps/miiverse-api/src/models/post.ts index 11eb963a..9107011c 100644 --- a/apps/miiverse-api/src/models/post.ts +++ b/apps/miiverse-api/src/models/post.ts @@ -7,29 +7,28 @@ import type { HydratedCommunityDocument } from '@/types/mongoose/community'; import type { PostToJSONOptions } from '@/types/mongoose/post-to-json-options'; import type { PostData, PostPainting, PostScreenshot, PostTopicTag } from '@/types/miiverse/post'; +/* Constraints here (default, required etc.) apply to new documents being added + * See IPost for expected shape of query results + * If you add default: or required:, please also update IPost and IPostInput! + */ const PostSchema = new Schema({ - id: String, - title_id: String, - screen_name: String, - body: String, - app_data: String, - painting: String, - screenshot: String, - screenshot_length: Number, - search_key: { - type: [String], - default: undefined - }, - topic_tag: { - type: String, - default: undefined - }, - community_id: { - type: String, - default: undefined - }, - created_at: Date, - feeling_id: Number, + id: { type: String, required: true }, + title_id: { type: String }, + screen_name: { type: String, required: true }, + body: { type: String, required: true }, + app_data: { type: String }, + + painting: { type: String }, + screenshot: { type: String }, + screenshot_length: { type: Number }, + + search_key: { type: [String] }, + topic_tag: { type: String }, + + community_id: { type: String, required: true }, + created_at: { type: Date, required: true }, + feeling_id: { type: Number }, + is_autopost: { type: Number, default: 0 @@ -46,6 +45,7 @@ const PostSchema = new Schema({ type: Number, default: 0 }, + empathy_count: { type: Number, default: 0, @@ -59,12 +59,15 @@ const PostSchema = new Schema({ type: Number, default: 1 }, - mii: String, - mii_face_url: String, - pid: Number, - platform_id: Number, - region_id: Number, - parent: String, + + mii: { type: String, required: true }, + mii_face_url: { type: String, required: true }, + + pid: { type: Number, required: true }, + platform_id: { type: Number }, + region_id: { type: Number }, + parent: { type: String }, + reply_count: { type: Number, default: 0 @@ -73,17 +76,21 @@ const PostSchema = new Schema({ type: Boolean, default: false }, + message_to_pid: { type: String, default: null }, + removed: { type: Boolean, default: false }, - removed_reason: String, - yeahs: [Number], - number: Number + removed_reason: { type: String }, + removed_by: { type: Number }, + removed_at: { type: Date }, + + yeahs: { type: [Number], default: [] } }, { id: false // * Disables the .id() getter used by Mongoose in TypeScript. Needed to have our own .id field }); @@ -114,12 +121,12 @@ PostSchema.method('cleanedMiiData', function cleanedMiiDat return this.mii.replace(/[^A-Za-z0-9+/=]/g, '').replace(/[\n\r]+/gm, '').trim(); }); -PostSchema.method('cleanedPainting', function cleanedPainting(): string { - return this.painting.replace(/[\n\r]+/gm, '').trim(); +PostSchema.method('cleanedPainting', function cleanedPainting(): string | undefined { + return this.painting?.replace(/[\n\r]+/gm, '').trim(); }); -PostSchema.method('cleanedAppData', function cleanedAppData(): string { - return this.app_data.replace(/[^A-Za-z0-9+/=]/g, '').replace(/[\n\r]+/gm, '').trim(); +PostSchema.method('cleanedAppData', function cleanedAppData(): string | undefined { + return this.app_data?.replace(/[^A-Za-z0-9+/=]/g, '').replace(/[\n\r]+/gm, '').trim(); }); PostSchema.method('formatPainting', function formatPainting(): PostPainting | undefined { @@ -146,7 +153,7 @@ PostSchema.method('formatTopicTag', function formatTopicTa if (this.topic_tag?.trim()) { return { name: this.topic_tag, - title_id: this.title_id + title_id: this.title_id ?? '' }; } }); @@ -158,26 +165,26 @@ PostSchema.method('json', function json(options: PostToJSO community_id: this.community_id, // TODO - This sucks country_id: this.country_id, created_at: moment(this.created_at).format('YYYY-MM-DD HH:MM:SS'), - feeling_id: this.feeling_id, + feeling_id: this.feeling_id ?? 0, id: this.id, is_autopost: this.is_autopost ? 1 : 0, is_community_private_autopost: this.is_community_private_autopost ? 1 : 0, is_spoiler: this.is_spoiler ? 1 : 0, is_app_jumpable: this.is_app_jumpable ? 1 : 0, - empathy_count: this.empathy_count || 0, + empathy_count: this.empathy_count, language_id: this.language_id, mii: undefined, // * Conditionally set later mii_face_url: undefined, // * Conditionally set later number: 0, painting: this.formatPainting(), pid: this.pid, - platform_id: this.platform_id, - region_id: this.region_id, - reply_count: this.reply_count || 0, + platform_id: this.platform_id ?? 1, + region_id: this.region_id ?? 0, + reply_count: this.reply_count, screen_name: this.screen_name, screenshot: this.formatScreenshot(), topic_tag: undefined, // * Conditionally set later - title_id: this.title_id + title_id: this.title_id ?? '' }; if (options.app_data) { diff --git a/apps/miiverse-api/src/server.ts b/apps/miiverse-api/src/server.ts index 55703e3b..d2637c51 100644 --- a/apps/miiverse-api/src/server.ts +++ b/apps/miiverse-api/src/server.ts @@ -1,4 +1,5 @@ import express from 'express'; +import 'express-async-errors'; // See package docs import expressMetrics from 'express-prom-bundle'; import { connect as connectDatabase } from '@/database'; import { logger } from '@/logger'; diff --git a/apps/miiverse-api/src/services/api/index.ts b/apps/miiverse-api/src/services/api/index.ts index 94faaa47..da0ada28 100644 --- a/apps/miiverse-api/src/services/api/index.ts +++ b/apps/miiverse-api/src/services/api/index.ts @@ -1,5 +1,6 @@ import express from 'express'; import subdomain from 'express-subdomain'; +import { rateLimit } from 'express-rate-limit'; import { LOG_INFO } from '@/logger'; import postsHandlers from '@/services/api/routes/posts'; import friendMessagesHandlers from '@/services/api/routes/friend_messages'; @@ -15,6 +16,15 @@ const router = express.Router(); // Router to handle the subdomain restriction const api = express.Router(); +// Global API ratelimit (todo: make this more fine-grained for heavy queries) +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + limit: 400, // 26 per minute + standardHeaders: true, + legacyHeaders: true +}); +router.use(limiter); + // Create subdomains LOG_INFO('[MIIVERSE] Creating \'api\' subdomain'); router.use(subdomain('api.olv', api)); diff --git a/apps/miiverse-api/src/services/api/routes/communities.ts b/apps/miiverse-api/src/services/api/routes/communities.ts index 1d4479fa..af40c7c5 100644 --- a/apps/miiverse-api/src/services/api/routes/communities.ts +++ b/apps/miiverse-api/src/services/api/routes/communities.ts @@ -298,7 +298,7 @@ router.post('/', multer().none(), async function (request: express.Request, resp return badRequest(response, ApiErrorCode.CREATE_TOO_MANY_FAVORITES, 403); } - const communitiesCount = await Community.count(); + const communitiesCount = await Community.countDocuments(); const communityID = (parseInt(parentCommunity.community_id) + (5000 * communitiesCount)); // Change this to auto increment const community = await Community.create({ platform_id: 0, // WiiU diff --git a/apps/miiverse-api/src/services/api/routes/friend_messages.ts b/apps/miiverse-api/src/services/api/routes/friend_messages.ts index 8e9764d6..7d048510 100644 --- a/apps/miiverse-api/src/services/api/routes/friend_messages.ts +++ b/apps/miiverse-api/src/services/api/routes/friend_messages.ts @@ -245,20 +245,20 @@ router.get('/', async function (request: express.Request, response: express.Resp is_app_jumpable: message.is_app_jumpable, empathy_added: message.empathy_count, language_id: message.language_id, - message_to_pid: message.message_to_pid, + message_to_pid: message.message_to_pid ?? undefined, mii: message.mii, mii_face_url: message.mii_face_url, - number: message.number || 0, + number: 0, pid: message.pid, platform_id: message.platform_id || 0, region_id: message.region_id || 0, reply_count: message.reply_count, screen_name: message.screen_name, topic_tag: { - name: message.topic_tag, + name: message.topic_tag ?? '', title_id: 0 }, - title_id: message.title_id + title_id: message.title_id ?? '' } }); } diff --git a/apps/miiverse-api/src/services/api/routes/posts.ts b/apps/miiverse-api/src/services/api/routes/posts.ts index 58b266c1..dd765958 100644 --- a/apps/miiverse-api/src/services/api/routes/posts.ts +++ b/apps/miiverse-api/src/services/api/routes/posts.ts @@ -22,7 +22,7 @@ import { Community } from '@/models/community'; import { config } from '@/config'; import { ApiErrorCode, badRequest, serverError } from '@/errors'; import type { PostRepliesResult } from '@/types/miiverse/post'; -import type { HydratedPostDocument } from '@/types/mongoose/post'; +import type { HydratedPostDocument, IPostInput } from '@/types/mongoose/post'; import type { HydratedCommunityDocument } from '@/types/mongoose/community'; import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; @@ -335,7 +335,7 @@ async function newPost(request: express.Request, response: express.Response): Pr searchKey = [searchKey]; } - const document = { + const document: IPostInput = { id: '', // * This gets changed when saving the document for the first time title_id: request.paramPack.title_id, community_id: community.olive_community_id, diff --git a/apps/miiverse-api/src/services/internal/contract/post.ts b/apps/miiverse-api/src/services/internal/contract/post.ts new file mode 100644 index 00000000..8e953a36 --- /dev/null +++ b/apps/miiverse-api/src/services/internal/contract/post.ts @@ -0,0 +1,108 @@ +import type { IPost } from '@/types/mongoose/post'; + +/* !!! HEY + * This type has a copy in apps/juxtaposition-ui/src/models/api/post.ts + * Make sure to copy over any modifications! */ + +/* This type is the contract for the frontend. If we make changes to the db, this shape should be kept. */ +export type PostDto = { + id: string; + title_id?: string; // number + screen_name: string; + body: string; + app_data?: string; // nintendo base64 + + painting?: string; // base64 or '', undef for PMs + screenshot?: string; // URL frag (leading /) or '', undef for PMs + screenshot_length?: number; + + search_key?: string[]; // can be [] + topic_tag?: string; // can be '' + + community_id: string; // number + created_at: string; // ISO Z + feeling_id?: number; + + is_autopost: boolean; + is_community_private_autopost: boolean; + is_spoiler: boolean; + is_app_jumpable: boolean; + + empathy_count: number; + country_id: number; + language_id: number; + + mii: string; // nintendo base64 + mii_face_url: string; // full URL (cdn., r2-cdn.) + + pid: number; + platform_id?: number; + region_id?: number; + parent: string | null; + + reply_count: number; + verified: boolean; + + message_to_pid: string | null; + removed: boolean; + removed_by?: number; + removed_at?: string; // ISO Z + removed_reason?: string; + + yeahs: number[]; +}; + +/** + * Maps a Post from the databse to the frontend contract post type. + * Doesn't do much for now, but as the database changes, this abstraction will carry more. + * @param post Database post type + * @returns API/frontend post type + */ +export function mapPost(post: IPost): PostDto { + return { + id: post.id, + title_id: post.title_id, + screen_name: post.screen_name, + body: post.body, + app_data: post.app_data, + + painting: post.painting, + screenshot: post.screenshot, + screenshot_length: post.screenshot_length, + + search_key: post.search_key, + topic_tag: post.topic_tag, + + community_id: post.community_id, + created_at: post.created_at.toISOString(), + feeling_id: post.feeling_id, + + is_autopost: !!post.is_autopost, + is_community_private_autopost: !!post.is_community_private_autopost, + is_spoiler: !!post.is_spoiler, + is_app_jumpable: !!post.is_app_jumpable, + + empathy_count: post.empathy_count, + country_id: post.country_id, + language_id: post.language_id, + + mii: post.mii, + mii_face_url: post.mii_face_url, + + pid: post.pid, + platform_id: post.platform_id, + region_id: post.region_id, + parent: post.parent ?? null, + + reply_count: post.reply_count, + verified: post.verified, + + message_to_pid: post.message_to_pid, + removed: post.removed, + removed_by: post.removed_by, + removed_at: post.removed_at?.toISOString(), + removed_reason: post.removed_reason, + + yeahs: post.yeahs + }; +} diff --git a/apps/miiverse-api/src/services/internal/errors.ts b/apps/miiverse-api/src/services/internal/errors.ts new file mode 100644 index 00000000..fa4a6aa2 --- /dev/null +++ b/apps/miiverse-api/src/services/internal/errors.ts @@ -0,0 +1,17 @@ +export class InternalAPIError extends Error { + status: number; + + constructor(status: number, message?: string) { + super(message); + this.status = status; + } +} + +export const errors = { + badRequest: InternalAPIError.bind(null, 400), + unauthorized: InternalAPIError.bind(null, 401), + forbidden: InternalAPIError.bind(null, 403), + notFound: InternalAPIError.bind(null, 404), + + serverError: InternalAPIError.bind(null, 500) +}; diff --git a/apps/miiverse-api/src/services/internal/index.ts b/apps/miiverse-api/src/services/internal/index.ts index 96ffa763..d0f22722 100644 --- a/apps/miiverse-api/src/services/internal/index.ts +++ b/apps/miiverse-api/src/services/internal/index.ts @@ -1,5 +1,7 @@ import express from 'express'; import { testRouter } from '@/services/internal/routes/test'; +import { postsRouter } from '@/services/internal/routes/posts'; export const internalApiRouter = express.Router(); internalApiRouter.use('/api/v1', testRouter); +internalApiRouter.use('/api/v1', postsRouter); diff --git a/apps/miiverse-api/src/services/internal/middleware/auth-accesscheck.ts b/apps/miiverse-api/src/services/internal/middleware/auth-accesscheck.ts new file mode 100644 index 00000000..d25a4c33 --- /dev/null +++ b/apps/miiverse-api/src/services/internal/middleware/auth-accesscheck.ts @@ -0,0 +1,41 @@ +import { errors } from '@/services/internal/errors'; +import type express from 'express'; + +/** + * Checks the user account is valid for this request (not banned, setup complete, etc.) + */ +export async function authAccessCheck(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + const account = response.locals.account; + if (account === null) { + // Guest access + return next(); + } + + if (account.pnid.deleted) { + throw new errors.unauthorized('Account does not exist'); + } + + if (account.pnid.accessLevel < 0 || + account.pnid.permissions?.bannedAllPermanently === true || + account.pnid.permissions?.bannedAllTemporarily === true) { + throw new errors.forbidden('Account has been banned'); + } + + // TODO console linking check (needs account server support) + + if (account.settings === null) { + // Account hasn't completed initial setup yet + // User guard will check this for endpoints that care + // Still.. spooky + return next(); + } + + // 0 = normal, 1 = limited from posting, 2 = temp ban, 3 = perma ban + if (account.settings.account_status !== 0 && account.settings.account_status !== 1) { + throw new errors.forbidden('Account has been banned from Juxtaposition'); + } + + // TODO lift expired temporary bans and post limits (frontend's responsibility for now) + + return next(); +} diff --git a/apps/miiverse-api/src/services/internal/middleware/auth-populate.ts b/apps/miiverse-api/src/services/internal/middleware/auth-populate.ts new file mode 100644 index 00000000..21969a8e --- /dev/null +++ b/apps/miiverse-api/src/services/internal/middleware/auth-populate.ts @@ -0,0 +1,81 @@ +import { getPIDFromServiceToken, getUserAccountData, getUserDataFromToken, getValueFromHeaders } from '@/util'; +import { errors } from '@/services/internal/errors'; +import { getUserSettings } from '@/database'; +import type express from 'express'; +import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; + +/** + * Handles authentication for service (NNAS/console) and OAuth (web) tokens. Sets locals.account to AccountData. + * Will error if tokens bad or account nonexistent, but otherwise does not check bans or setup status. + */ +export async function authPopulate(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + // Used by console applets + const serviceToken = getValueFromHeaders(request.headers, 'x-service-token'); + // Used by web frontend + const oAuthToken = getValueFromHeaders(request.headers, 'x-oauth-token'); + + if (serviceToken && oAuthToken) { + throw new errors.badRequest('Multiple authentication tokens provided'); + } + + let pnid: GetUserDataResponse | null = null; + if (serviceToken) { + pnid = await consoleAuth(request, serviceToken); + } else if (oAuthToken) { + pnid = await webAuth(request, oAuthToken); + } + + if (pnid !== null) { + // Null here just means the initial setup isn't done + const settings = await getUserSettings(pnid.pid); + + const moderator = accountIsModerator(pnid); + + response.locals.account = { pnid, settings, moderator }; + } else { + // Guest access + response.locals.account = null; + } + + return next(); +} + +async function consoleAuth(_request: express.Request, serviceToken: string): Promise { + const pid = getPIDFromServiceToken(serviceToken); + if (pid === null) { + throw new errors.unauthorized('Invalid service token'); + } + + // If the user has a valid token for an unknown PID, just let the exception bubble + const pnid = await getUserAccountData(pid); + + return pnid; +} + +async function webAuth(request: express.Request, oAuthToken: string): Promise { + // The "normal" getUserData API (used here) is mutually incompatible with the "backdoor" one. + // Since we can only use the backdoor one for consoles right now... + const pid = (await getUserDataFromToken(oAuthToken).catch((e) => { + // TODO should probably check the error type here in case of e.g. connection refused + request.log.error(e, 'Failed to get user data from OAuth token'); + throw new errors.unauthorized('Invalid OAuth token!'); + })).pid; + // Ask the "backdoor" API, just use the above as a glorified token decryption. + const pnid = await getUserAccountData(pid); + + return pnid; +} + +function accountIsModerator(pnid: GetUserDataResponse): boolean { + // AL2 can always moderate... + if (pnid.accessLevel >= 2) { + return true; + } + + // Lower-level accounts can also have permission granted + if (pnid.permissions?.moderateMiiverse === true) { + return true; + } + + return false; +} diff --git a/apps/miiverse-api/src/services/internal/middleware/guards.ts b/apps/miiverse-api/src/services/internal/middleware/guards.ts new file mode 100644 index 00000000..5feb88f0 --- /dev/null +++ b/apps/miiverse-api/src/services/internal/middleware/guards.ts @@ -0,0 +1,55 @@ +import { errors } from '@/services/internal/errors'; +import type express from 'express'; + +/** + * Guest access is fine + */ +export async function guest(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + return next(); +} + +/** + * Fail on guest access + */ +export async function user(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + const account = response.locals.account; + if (account === null) { + // Guest access + throw new errors.unauthorized('Authentication token not provided'); + } + + if (account.settings === null) { + // Most endpoints expect users to have completed the account setup flow + // TODO eventually this will need a carveout for the setup flow itself + throw new errors.forbidden('Account setup not complete'); + } + + return next(); +} + +/** + * Moderators only + */ +export async function moderator(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + const account = response.locals.account; + if (account === null) { + // Guest access + throw new errors.unauthorized('Authentication token not provided'); + } + + if (account.settings === null) { + throw new errors.forbidden('Account setup not complete'); + } + + if (account.moderator !== true) { + throw new errors.forbidden('You cannot access this endpoint'); + } + + return next(); +} + +export const guards = { + guest, + user, + moderator +}; diff --git a/apps/miiverse-api/src/services/internal/routes/posts.ts b/apps/miiverse-api/src/services/internal/routes/posts.ts new file mode 100644 index 00000000..5a0216a0 --- /dev/null +++ b/apps/miiverse-api/src/services/internal/routes/posts.ts @@ -0,0 +1,17 @@ +import express from 'express'; +import { getPostByID } from '@/database'; +import { errors } from '@/services/internal/errors'; +import { handle } from '@/services/internal/utils'; +import { guards } from '@/services/internal/middleware/guards'; +import { mapPost } from '@/services/internal/contract/post'; + +export const postsRouter = express.Router(); + +postsRouter.get('/posts/:post_id', guards.guest, handle(async ({ req, res }) => { + const post = await getPostByID(req.params.post_id); + if (!post || (post.removed && res.locals.account?.moderator !== true)) { + throw new errors.notFound('Post not found'); + } + + return mapPost(post); +})); diff --git a/apps/miiverse-api/src/services/internal/routes/test.ts b/apps/miiverse-api/src/services/internal/routes/test.ts index 297cee54..8a766ac3 100644 --- a/apps/miiverse-api/src/services/internal/routes/test.ts +++ b/apps/miiverse-api/src/services/internal/routes/test.ts @@ -1,9 +1,10 @@ import express from 'express'; import { handle } from '@/services/internal/utils'; +import { guards } from '@/services/internal/middleware/guards'; export const testRouter = express.Router(); -testRouter.get('/test', handle(async () => { +testRouter.get('/test', guards.user, handle(async () => { return { message: 'Hello from the test route!' }; diff --git a/apps/miiverse-api/src/services/internal/server.ts b/apps/miiverse-api/src/services/internal/server.ts index 0e63d0cc..eca7b22d 100644 --- a/apps/miiverse-api/src/services/internal/server.ts +++ b/apps/miiverse-api/src/services/internal/server.ts @@ -5,8 +5,37 @@ import { MiiverseServiceDefinition } from '@repo/grpc-client/out/miiverse_servic import { config } from '@/config'; import { internalApiRouter } from '@/services/internal'; import { logger } from '@/logger'; +import { InternalAPIError } from '@/services/internal/errors'; +import { loggerHttp } from '@/loggerHttp'; +import { authPopulate } from '@/services/internal/middleware/auth-populate'; +import { authAccessCheck } from '@/services/internal/middleware/auth-accesscheck'; import type { CallContext, ServerMiddlewareCall } from 'nice-grpc'; +// API server + +const app = express(); +app.use(express.json()); +app.use(loggerHttp); +app.use(authPopulate); +app.use(authAccessCheck); +app.use(internalApiRouter); + +// API error handler +app.use((err: Error, _request: express.Request, response: express.Response, next: express.NextFunction) => { + if (!(err instanceof InternalAPIError)) { + return next(err); + } + + response.status(err.status).json({ message: err.message }); +}); +// Javascript error handler +app.use((err: Error, _request: express.Request, response: express.Response, _next: express.NextFunction) => { + response.err = err; // For Pino + response.status(500).json({ message: 'Internal server error' }); +}); + +// gRPC glue + export async function* apiKeyMiddleware( call: ServerMiddlewareCall, context: CallContext @@ -20,10 +49,6 @@ export async function* apiKeyMiddleware( return yield* call.next(call.request, context); } -const app = express(); -app.use(express.json()); -app.use(internalApiRouter); - const allowedMethods = ['get', 'post', 'put', 'delete', 'patch'] as const; const methodsWithBody = ['post', 'put', 'delete', 'patch'] as const; type AllowedMethods = typeof allowedMethods[number]; @@ -37,9 +62,10 @@ export async function setupGrpc(): Promise { throw new ServerError(Status.UNIMPLEMENTED, 'Method not implemented'); } const method = request.method.toLowerCase() as AllowedMethods; + const headers = JSON.parse(request.headers); const hasBody = methodsWithBody.includes(method as any); - let baseRequest = superRequest(app)[method](request.path); + let baseRequest = superRequest(app)[method](request.path).set(headers); if (hasBody) { baseRequest = baseRequest.send(JSON.parse(request.payload)); } diff --git a/apps/miiverse-api/src/services/internal/utils.ts b/apps/miiverse-api/src/services/internal/utils.ts index dc31163b..94e5c0c8 100644 --- a/apps/miiverse-api/src/services/internal/utils.ts +++ b/apps/miiverse-api/src/services/internal/utils.ts @@ -1,4 +1,3 @@ -import { logger } from '@/logger'; import type { Request, Response } from 'express'; export type HandlerContext = { @@ -10,15 +9,8 @@ export type HandlerFunction = (ctx: HandlerContext) => any; export function handle(handler: HandlerFunction) { return async (req: Request, res: Response): Promise => { - try { - const ctx: HandlerContext = { req, res }; - const result = await handler(ctx); - res.status(200).json(result); - return; - } catch (error) { - logger.error('Unhandled exception in handler', error); - res.status(500).json({ error: 'Internal Server Error' }); - return; - } + const ctx: HandlerContext = { req, res }; + const result = await handler(ctx); + res.status(200).json(result); }; } diff --git a/apps/miiverse-api/src/types/common/account-data.ts b/apps/miiverse-api/src/types/common/account-data.ts new file mode 100644 index 00000000..3f885a64 --- /dev/null +++ b/apps/miiverse-api/src/types/common/account-data.ts @@ -0,0 +1,8 @@ +import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; +import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; + +export interface AccountData { + pnid: GetUserDataResponse; + settings: HydratedSettingsDocument | null; + moderator: boolean; +} diff --git a/apps/miiverse-api/src/types/common/service-token.ts b/apps/miiverse-api/src/types/common/service-token.ts new file mode 100644 index 00000000..985f08ae --- /dev/null +++ b/apps/miiverse-api/src/types/common/service-token.ts @@ -0,0 +1,8 @@ +export interface ServiceToken { + system_type: number; + token_type: number; + pid: number; + access_level: number; + title_id: bigint; + issue_time: bigint; +} diff --git a/apps/miiverse-api/src/types/common/system-types.ts b/apps/miiverse-api/src/types/common/system-types.ts new file mode 100644 index 00000000..1edbac75 --- /dev/null +++ b/apps/miiverse-api/src/types/common/system-types.ts @@ -0,0 +1,6 @@ +export enum SystemType { + WUP = 1, + CTR = 2, + API = 3, + PasswordReset = 0xFF // * This kinda blows +} diff --git a/apps/miiverse-api/src/types/common/token-types.ts b/apps/miiverse-api/src/types/common/token-types.ts new file mode 100644 index 00000000..241372c2 --- /dev/null +++ b/apps/miiverse-api/src/types/common/token-types.ts @@ -0,0 +1,7 @@ +export enum TokenType { + OAuthAccess = 1, + OAuthRefresh = 2, + NEX = 3, + IndependentService = 4, + PasswordReset = 5 +} diff --git a/apps/miiverse-api/src/types/express.d.ts b/apps/miiverse-api/src/types/express.d.ts index 1782a372..31150624 100644 --- a/apps/miiverse-api/src/types/express.d.ts +++ b/apps/miiverse-api/src/types/express.d.ts @@ -1,6 +1,7 @@ import type { ParamPack } from '@/types/common/param-pack'; import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; import type { ApiErrorCode } from '@/errors'; +import type { AccountData } from '@/types/common/account-data'; declare global { namespace Express { @@ -13,5 +14,9 @@ declare global { interface Response { errorCode?: ApiErrorCode; } + // Locals used in internal/grpc + interface Locals { + account: AccountData | null; + } } } diff --git a/apps/miiverse-api/src/types/mongoose/post.ts b/apps/miiverse-api/src/types/mongoose/post.ts index dffb3942..a9354c7d 100644 --- a/apps/miiverse-api/src/types/mongoose/post.ts +++ b/apps/miiverse-api/src/types/mongoose/post.ts @@ -3,41 +3,73 @@ import type { HydratedCommunityDocument } from '@/types/mongoose/community'; import type { PostToJSONOptions } from '@/types/mongoose/post-to-json-options'; import type { PostData, PostPainting, PostScreenshot, PostTopicTag } from '@/types/miiverse/post'; +/* This type needs to reflect "reality" as it is in the DB + * Thus, all the optionals, since some legacy documents are missing many fields + */ export interface IPost { id: string; - title_id: string; + title_id?: string; // u64 screen_name: string; body: string; - app_data: string; - painting: string; - screenshot: string; - screenshot_length: number; - search_key: string[]; - topic_tag: string; - community_id: string; + app_data?: string; // nintendo base64 + + painting?: string; // base64, can be empty or undefined + screenshot?: string; // url fragment (leading /), can be empty or undefined + screenshot_length?: number; + + search_key?: string[]; // can be empty or undefined + topic_tag?: string; + + community_id: string; // actually a number created_at: Date; - feeling_id: number; - is_autopost: number; - is_community_private_autopost?: number; - is_spoiler: number; - is_app_jumpable: number; - empathy_count?: number; + feeling_id?: number; // missing on PMs + + is_autopost: number; // 0 | 1 + is_community_private_autopost: number; // 0 | 1 + is_spoiler: number; // 0 | 1 + is_app_jumpable: number; // 0 | 1 + + empathy_count: number; country_id: number; language_id: number; - mii: string; - mii_face_url: string; + + mii: string; // nintendo base64 + mii_face_url: string; // fully qualified (usually cdn. or r2-cdn.) + pid: number; - platform_id: number; - region_id: number; - parent: string; - reply_count?: number; + platform_id?: number; // missing on PMs + region_id?: number; // missing on PMs + parent?: string | null; // both undef and null exist in db + + reply_count: number; verified: boolean; - message_to_pid?: string; + + message_to_pid: string | null; + removed: boolean; removed_reason?: string; - yeahs?: Types.Array; - number?: number; + removed_by?: number; + removed_at?: Date; + + yeahs: Types.Array; } +// Fields that have "default: " in the Mongoose schema should also be listed here to make them optional +// on input but not output +// We really need an ORM +type PostDefaultedFields = + 'is_autopost' | + 'is_community_private_autopost' | + 'is_spoiler' | + 'is_app_jumpable' | + 'empathy_count' | + 'country_id' | + 'language_id' | + 'reply_count' | + 'verified' | + 'message_to_pid' | + 'removed' | + 'yeahs'; +export type IPostInput = Omit & Partial>; export interface IPostMethods { del(reason: string): Promise; @@ -52,6 +84,6 @@ export interface IPostMethods { json(options: PostToJSONOptions, community?: HydratedCommunityDocument): PostData; } -export type PostModel = Model; +export type PostModel = Model; export type HydratedPostDocument = HydratedDocument; diff --git a/apps/miiverse-api/src/util.ts b/apps/miiverse-api/src/util.ts index d15a4b08..8e0033a0 100644 --- a/apps/miiverse-api/src/util.ts +++ b/apps/miiverse-api/src/util.ts @@ -8,14 +8,18 @@ import { createChannel, createClient, Metadata } from 'nice-grpc'; import crc32 from 'crc/crc32'; import { FriendsDefinition } from '@pretendonetwork/grpc/friends/friends_service'; import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service'; +import { APIDefinition } from '@pretendonetwork/grpc/api/api_service'; import { config } from '@/config'; import { logger } from '@/logger'; +import { SystemType } from '@/types/common/system-types'; +import { TokenType } from '@/types/common/token-types'; import type { FriendRequest } from '@pretendonetwork/grpc/friends/friend_request'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { GetUserDataResponse as AccountGetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { GetUserDataResponse as ApiGetUserDataResponse } from '@pretendonetwork/grpc/api/get_user_data_rpc'; import type { ParsedQs } from 'qs'; import type { IncomingHttpHeaders } from 'node:http'; import type { ParamPack } from '@/types/common/param-pack'; -import type { ServiceToken } from '@/types/common/token'; +import type { ServiceToken } from '@/types/common/service-token'; // * nice-grpc doesn't export ChannelImplementation so this can't be typed const gRPCFriendsChannel = createChannel(`${config.grpc.friends.host}:${config.grpc.friends.port}`); @@ -24,6 +28,9 @@ const gRPCFriendsClient = createClient(FriendsDefinition, gRPCFriendsChannel); const gRPCAccountChannel = createChannel(`${config.grpc.account.host}:${config.grpc.account.port}`); const gRPCAccountClient = createClient(AccountDefinition, gRPCAccountChannel); +const gRPCApiChannel = createChannel(`${config.grpc.account.host}:${config.grpc.account.port}`); +const gRPCApiClient = createClient(APIDefinition, gRPCApiChannel); + const s3 = new aws.S3({ endpoint: new aws.Endpoint(config.s3.endpoint), accessKeyId: config.s3.key, @@ -34,44 +41,67 @@ const s3 = new aws.S3({ export const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu; export function decodeParamPack(paramPack: string): ParamPack { - const values = Buffer.from(paramPack, 'base64').toString().split('\\'); - const entries = values.filter(value => value).reduce((entries: string[][], value: string, index: number) => { - if (0 === index % 2) { - entries.push([value]); - } else { - entries[Math.ceil(index / 2 - 1)].push(value); + const values = Buffer.from(paramPack, 'base64').toString().split('\\').filter(v => v.length > 0).values(); + const entries: Record = {}; + for (let i = 0; i < 16; i++) { /* Enforce an upper limit on ParamPack decoding */ + // Keys and values are sibling list entries + const paramKey = values.next().value; + const paramVal = values.next().value; + // We hit the end of the list + if (paramKey === undefined || paramVal === undefined) { + break; } - return entries; - }, []); + entries[paramKey] = paramVal; + } - return Object.fromEntries(entries); + // normalize and prevent any funny businiess from clients + // one day this can be a proper DTO + return { + title_id: entries.title_id ?? '', + access_key: entries.access_key ?? '', + platform_id: entries.platform_id ?? '', + region_id: entries.region_id ?? '', + language_id: entries.language_id ?? '', + country_id: entries.country_id ?? '', + area_id: entries.area_id ?? '', + network_restriction: entries.network_restriction ?? '', + friend_restriction: entries.friend_restriction ?? '', + rating_restriction: entries.rating_restriction ?? '', + rating_organization: entries.rating_organization ?? '', + transferable_id: entries.transferable_id ?? '', + tz_name: entries.tz_name ?? '', + utc_offset: entries.utc_offset ?? '' + }; } -export function getPIDFromServiceToken(token: string): number { +export function getPIDFromServiceToken(token: string): number | null { try { const decryptedToken = decryptToken(Buffer.from(token, 'base64')); if (!decryptedToken) { - return 0; + return null; } const unpackedToken = unpackServiceToken(decryptedToken); + if (unpackedToken === null) { + return null; + } // * Only allow token types 1 (Wii U) and 2 (3DS) - if (unpackedToken.system_type !== 1 && unpackedToken.system_type !== 2) { - return 0; + if (unpackedToken.system_type !== SystemType.CTR && unpackedToken.system_type !== SystemType.WUP) { + return null; } // * Check if the token is expired if (unpackedToken.issue_time + (24n * 3600n * 1000n) < Date.now()) { - return 0; + return null; } return unpackedToken.pid; } catch (e) { logger.error(e, 'Failed to extract PID from service token'); - return 0; + return null; } } @@ -95,7 +125,12 @@ export function decryptToken(token: Buffer): Buffer { return decrypted; } -export function unpackServiceToken(token: Buffer): ServiceToken { +export function unpackServiceToken(token: Buffer): ServiceToken | null { + const token_type = token.readUInt8(0x1); + if (token_type !== TokenType.IndependentService) { + return null; + } + return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), @@ -186,7 +221,7 @@ export async function getUserFriendRequestsIncoming(pid: number): Promise { +export function getUserAccountData(pid: number): Promise { return gRPCAccountClient.getUserData({ pid: pid }, { @@ -196,6 +231,15 @@ export function getUserAccountData(pid: number): Promise { }); } +export function getUserDataFromToken(token: string): Promise { + return gRPCApiClient.getUserData({}, { + metadata: Metadata({ + 'X-API-Key': config.grpc.account.apiKey, + 'X-Token': token + }) + }); +} + export function getValueFromQueryString(qs: ParsedQs, key: string): string[] { const property = qs[key] as string | string[]; diff --git a/package-lock.json b/package-lock.json index e9d1ce75..0d33b671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "ejs": "^3.1.10", "esbuild-plugin-copy": "^2.1.1", "express": "^4.21.2", + "express-async-errors": "^3.1.1", "express-prom-bundle": "^7.0.2", "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", @@ -53,7 +54,8 @@ "redis": "^4.7.0", "sharp": "^0.33.5", "tga": "^1.0.7", - "tsx": "^4.19.3" + "tsx": "^4.19.3", + "zod": "^3.25.56" }, "devDependencies": { "@pretendonetwork/eslint-config": "^0.0.11", @@ -85,11 +87,13 @@ "colors": "^1.4.0", "crc": "^4.3.2", "express": "^4.17.1", + "express-async-errors": "^3.1.1", "express-prom-bundle": "^7.0.2", + "express-rate-limit": "^7.5.0", "express-subdomain": "^1.0.5", "fs-extra": "^9.0.0", "moment": "^2.24.0", - "mongoose": "^6.10.1", + "mongoose": "^8.15.1", "multer": "^1.4.5-lts.1", "nice-grpc": "^2.1.12", "node-snowflake": "0.0.1", @@ -126,6 +130,24 @@ "xmlbuilder2": "^3.1.0" } }, + "apps/miiverse-api/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "apps/miiverse-api/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "apps/miiverse-api/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -140,6 +162,105 @@ "node": ">=10" } }, + "apps/miiverse-api/node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "apps/miiverse-api/node_modules/mongodb": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", + "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "apps/miiverse-api/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "apps/miiverse-api/node_modules/mongoose": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.1.tgz", + "integrity": "sha512-RhQ4DzmBi5BNGcS0w4u1vdMRIKcteXTCNzDt1j7XRcdWYBz1MjMjulBhPaeC5jBCHOD1yinuOFTTSOWLLGexWw==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.16.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "apps/miiverse-api/node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "apps/miiverse-api/node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -153,6 +274,37 @@ "node": ">=10.13.0" } }, + "apps/miiverse-api/node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "apps/miiverse-api/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "apps/miiverse-api/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -2663,7 +2815,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", - "optional": true, "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -7232,6 +7383,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", + "license": "ISC", + "peerDependencies": { + "express": "^4.16.2" + } + }, "node_modules/express-prom-bundle": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-7.0.2.tgz", @@ -7253,6 +7413,7 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -9289,8 +9450,7 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/memorystream": { "version": "0.3.1", @@ -12316,7 +12476,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, "dependencies": { "memory-pager": "^1.0.2" } @@ -14154,9 +14313,10 @@ } }, "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "version": "3.25.56", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.56.tgz", + "integrity": "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/protobufs/miiverse/v2/packet.proto b/protobufs/miiverse/v2/packet.proto index e5ef416a..928253c9 100644 --- a/protobufs/miiverse/v2/packet.proto +++ b/protobufs/miiverse/v2/packet.proto @@ -5,7 +5,8 @@ package miiverse.v2; message Packet { string path = 1; string method = 2; - string payload = 3; + string headers = 3; + string payload = 4; } message PacketResponse {