- **Allow remote group moderators to edit group events and posts**

- **Allow events to hold metadata information, either preconfigured (live video URL, price details, accessibility informations,…), either through a free key/value form.** Metadata concerning live video feeds linking to PeerTube, YouTube & Twitch will benefit from iframe integration.
 - Add the possibility to create profiles and groups from CLI
 - Add the possibility to create a profile at the same time when creating an user from CLI
 - Add the possibility to create users with LDAP provider from CLI
 - Added back support for Docker-compose based development
 - Added rel=canonical and meta robots noindex tags to public pages from remote groups, in order to avoid them being indexed by Google
 - Allow members-restricted posts to be viewable by instance moderators (for moderation purposes)
 - Added a filter to resize pictures bigger than 1920x1080
 - Allow to deny registration by email or email domain
 - Added missing index on participants url
 - Added a loading wheel to show that events are loading on some views
 
 - Made server only listen on IPv4 in the install template
 - Improve identity picker to have a fixed height and allow filtering between your identities and group contacts
 - Leaflet map controls (zoom/locate) are now translatable
 - Show exactly 12 events on the Explore page
 
 - Fixed links contained in event & post description that didn't open in new tabs
 - Add back missing RSS/ical links on public group pages
 - Fixed links to Framacolibri forum
 - Fixed drafts and restricted visibility events & posts listed on group page
 - Fixed notification page on Safari
 - Fixed profile edition
 - Fixed Feed Token recreation
 - Fixed media cleaner job
 - Fixed english being always used as a language instead of the default one set when the request has no `Accept-Language` header (such as Google index bots)
 - Fixed Ecto validation errors not being translated and interpolated
 - Fixed <html> `lang` attribute not being properly set with the language currently used
 - Fixed federated posts having wrong visibility setting
 - Fixed unused CSS filter on homepage rendering wrong on Webkit
 - Fixed handling SSL being already started in LDAP connection
 - Fixed an Apollo cache issue when registrering your first profile
 - Fixed the Docker image missing ca-certificates
 - Fixed missing pagination on Explore page featured events
 - Fixed broken popup warning when editing an event
 - Fixed GraphQL Playground (again)
 - Fixed Coordinates mixmatch between latitude and longitude in iCalendar export and federation
 - Fixed token refreshment issues
 - Fixed search from 404 page
 
 - Catalan
 - Chinese (Traditional)
 - Finnish
 - French
 - Gaelic
 - Galician
 - German
 - Indonesian
 - Russian
 - Spanish
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEExMITpfxOHHCvHn8FoGG53eDKB3MFAmEbjvoACgkQoGG53eDK
 B3NOlhAAsAg9L5KFrbsxGtUDL0tCZWtOZXub9YBdeXThykCGcvJleXYDi4FenQN7
 WYSi5gBGZTHJCbEy3AatkaraYV+w7oExB9dKj882iToLj7M16X4c+pWeZNgBt5zb
 oEf4CG/BvV0ICKn48khmsAQVbAPkm8k3Um7fELwfZ2pA5CKvHLyrD5VUnXaz5abV
 in46GDERPFUZ0cSXoozK0PwEP473KGlGWM8vXRkudLCm0aZzx9UwxdJFcvmDMMuK
 etVLiH/XlPyoE89LA0Vq0gUEozQrbAjaXAQCkiaftyKMpw0hda64rz80L4Hy/FS8
 RqNWVDvznrcRe7FmsKDzkgkBfk4ecero60MAE1N2rfJbwwYFVGSD1G2B/PckN0ss
 /4G2Dn61tn/zhBDNVerWRbaHg7cyIbxb92GVOmAJK5yvOG5qo6h8gwN2OzSLSjWG
 jTbIFgJ7KY9XVcFwdZ4VL95CglzM32irFbFh7kpx3MWVAYoeBDPcevE+q8zRyUCY
 jYbWLeTGWlNyEe+C5mXT9IrGt51yFIbsT6alst28H9pOyHZQnsjGzXRIYCn5nNne
 TTbkNG0fE5Pl1Xo40WaLcYJIWjU/2rwS9tyrWmcN5Ws5qg33mRdGKlU6AaSuGfkG
 QpGcclyrvsYl9bVX/XHWbJnAZUdm+qB59IHELY37dLYM2ASUk6w=
 =SXZE
 -----END PGP SIGNATURE-----

Merge tag '1.3.0' into chapril

- **Allow remote group moderators to edit group events and posts**
- **Allow events to hold metadata information, either preconfigured (live video URL, price details, accessibility informations,…), either through a free key/value form.** Metadata concerning live video feeds linking to PeerTube, YouTube & Twitch will benefit from iframe integration.
- Add the possibility to create profiles and groups from CLI
- Add the possibility to create a profile at the same time when creating an user from CLI
- Add the possibility to create users with LDAP provider from CLI
- Added back support for Docker-compose based development
- Added rel=canonical and meta robots noindex tags to public pages from remote groups, in order to avoid them being indexed by Google
- Allow members-restricted posts to be viewable by instance moderators (for moderation purposes)
- Added a filter to resize pictures bigger than 1920x1080
- Allow to deny registration by email or email domain
- Added missing index on participants url
- Added a loading wheel to show that events are loading on some views

- Made server only listen on IPv4 in the install template
- Improve identity picker to have a fixed height and allow filtering between your identities and group contacts
- Leaflet map controls (zoom/locate) are now translatable
- Show exactly 12 events on the Explore page

- Fixed links contained in event & post description that didn't open in new tabs
- Add back missing RSS/ical links on public group pages
- Fixed links to Framacolibri forum
- Fixed drafts and restricted visibility events & posts listed on group page
- Fixed notification page on Safari
- Fixed profile edition
- Fixed Feed Token recreation
- Fixed media cleaner job
- Fixed english being always used as a language instead of the default one set when the request has no `Accept-Language` header (such as Google index bots)
- Fixed Ecto validation errors not being translated and interpolated
- Fixed <html> `lang` attribute not being properly set with the language currently used
- Fixed federated posts having wrong visibility setting
- Fixed unused CSS filter on homepage rendering wrong on Webkit
- Fixed handling SSL being already started in LDAP connection
- Fixed an Apollo cache issue when registrering your first profile
- Fixed the Docker image missing ca-certificates
- Fixed missing pagination on Explore page featured events
- Fixed broken popup warning when editing an event
- Fixed GraphQL Playground (again)
- Fixed Coordinates mixmatch between latitude and longitude in iCalendar export and federation
- Fixed token refreshment issues
- Fixed search from 404 page

- Catalan
- Chinese (Traditional)
- Finnish
- French
- Gaelic
- Galician
- German
- Indonesian
- Russian
- Spanish
This commit is contained in:
Tykayn 2021-08-18 15:48:10 +02:00 committed by tykayn
commit f66845efa4
277 changed files with 15042 additions and 6152 deletions

25
.env.template Normal file
View File

@ -0,0 +1,25 @@
# Database settings
POSTGRES_USER=mobilizon
POSTGRES_PASSWORD=changethis
POSTGRES_DB=mobilizon
POSTGRES_PORT=5432
# Instance configuration
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=false
MOBILIZON_INSTANCE_NAME=My Mobilizon Instance
MOBILIZON_INSTANCE_HOST=mobilizon.lan
MOBILIZON_INSTANCE_PORT=4000
MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis
MOBILIZON_INSTANCE_SECRET_KEY=changethis
MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan
MOBILIZON_REPLY_EMAIL=contact@mobilizon.lan
# Email settings
MOBILIZON_SMTP_SERVER=localhost
MOBILIZON_SMTP_PORT=25
MOBILIZON_SMTP_HOSTNAME=localhost
MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan
MOBILIZON_SMTP_PASSWORD=password
MOBILIZON_SMTP_SSL=false

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ release/
docker/production/.env
test-junit-report.xml
js/junit.xml
.env

View File

@ -235,7 +235,7 @@ package-app:
only:
- tags@framasoft/mobilizon
artifacts:
expire_in: never
expire_in: 30 days
paths:
- release

View File

@ -8,3 +8,4 @@
73B351E4CB3AF715AD450A085F5E6304
BBACD7F0BACD4A6D3010C26604671692
6D4D4A4821B93BCFAC9CDBB367B34C4B
5674F0D127852889ED0132DC2F442AAB

View File

@ -5,6 +5,69 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.3.0 - 2021-08-12
### Added
- **Allow remote group moderators to edit group events and posts**
- **Allow events to hold metadata information, either preconfigured (live video URL, price details, accessibility informations,…), either through a free key/value form.** Metadata concerning live video feeds linking to PeerTube, YouTube & Twitch will benefit from iframe integration.
- Add the possibility to create profiles and groups from CLI
- Add the possibility to create a profile at the same time when creating an user from CLI
- Add the possibility to create users with LDAP provider from CLI
- Added back support for Docker-compose based development
- Added rel=canonical and meta robots noindex tags to public pages from remote groups, in order to avoid them being indexed by Google
- Allow members-restricted posts to be viewable by instance moderators (for moderation purposes)
- Added a filter to resize pictures bigger than 1920x1080
- Allow to deny registration by email or email domain
- Added missing index on participants url
- Added a loading wheel to show that events are loading on some views
### Changed
- Made server only listen on IPv4 in the install template
- Improve identity picker to have a fixed height and allow filtering between your identities and group contacts
- Leaflet map controls (zoom/locate) are now translatable
- Show exactly 12 events on the Explore page
### Fixed
- Fixed links contained in event & post description that didn't open in new tabs
- Add back missing RSS/ical links on public group pages
- Fixed links to Framacolibri forum
- Fixed drafts and restricted visibility events & posts listed on group page
- Fixed notification page on Safari
- Fixed profile edition
- Fixed Feed Token recreation
- Fixed media cleaner job
- Fixed english being always used as a language instead of the default one set when the request has no `Accept-Language` header (such as Google index bots)
- Fixed Ecto validation errors not being translated and interpolated
- Fixed <html> `lang` attribute not being properly set with the language currently used
- Fixed federated posts having wrong visibility setting
- Fixed unused CSS filter on homepage rendering wrong on Webkit
- Fixed handling SSL being already started in LDAP connection
- Fixed an Apollo cache issue when registrering your first profile
- Fixed the Docker image missing ca-certificates
- Fixed missing pagination on Explore page featured events
- Fixed broken popup warning when editing an event
- Fixed GraphQL Playground (again)
- Fixed Coordinates mixmatch between latitude and longitude in iCalendar export and federation
- Fixed token refreshment issues
- Fixed search from 404 page
### Translations
- Catalan
- Chinese (Traditional)
- Finnish
- French
- Gaelic
- Galician
- German
- Indonesian
- Russian
- Spanish
## 1.2.3 - 2021-07-02
### Changed

View File

@ -1,6 +1,6 @@
FROM bitwalker/alpine-elixir:latest
FROM elixir:alpine
RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses
RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses git
RUN mix local.hex --force && mix local.rebar --force

View File

@ -2,6 +2,13 @@ init:
@bash docker/message.sh "start"
make start
setup: stop
@bash docker/message.sh "Compiling everything"
docker-compose run --rm api bash -c 'mix deps.get; yarn --cwd "js"; yarn --cwd "js" build:pictures; mix ecto.create; mix ecto.migrate'
migrate:
docker-compose run --rm api mix ecto.migrate
logs:
docker-compose logs -f
start: stop
@bash docker/message.sh "starting Mobilizon with docker"
docker-compose up -d api

View File

@ -18,6 +18,7 @@ config :mobilizon, :instance,
hostname: "localhost",
registrations_open: true,
registration_email_allowlist: [],
registration_email_denylist: [],
languages: [],
default_language: "fr",
demo: false,
@ -65,9 +66,11 @@ config :mime, :types, %{
config :mobilizon, Mobilizon.Web.Upload,
uploader: Mobilizon.Web.Upload.Uploader.Local,
filters: [
Mobilizon.Web.Upload.Filter.Dedupe,
Mobilizon.Web.Upload.Filter.AnalyzeMetadata,
Mobilizon.Web.Upload.Filter.Optimize
Mobilizon.Web.Upload.Filter.Resize,
Mobilizon.Web.Upload.Filter.Optimize,
Mobilizon.Web.Upload.Filter.BlurHash,
Mobilizon.Web.Upload.Filter.Dedupe
],
allow_list_mime_types: ["image/gif", "image/jpeg", "image/png", "image/webp"],
link_name: true,

View File

@ -1,21 +1,15 @@
import Config
# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with brunch.io to recompile .js and .css sources.
config :mobilizon, Mobilizon.Web.Endpoint,
http: [
ip: {127, 0, 0, 1},
port: 4000
port: String.to_integer(System.get_env("MOBILIZON_INSTANCE_HOST_PORT", "4000"))
],
url: [
host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.local"),
port: 80,
port: String.to_integer(System.get_env("MOBILIZON_INSTANCE_HOST_PORT", "80")),
scheme: "http"
],
secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "changethis"),
debug_errors: true,
code_reloader: true,
check_origin: false,
@ -91,6 +85,9 @@ config :mobilizon, :instance,
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN") == "true",
groups: true
config :mobilizon, Mobilizon.Web.Auth.Guardian,
secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "changethis")
# config :mobilizon, :activitypub, sign_object_fetches: false
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"

View File

@ -2,7 +2,7 @@
import Config
listen_ip = System.get_env("MOBILIZON_INSTANCE_LISTEN_IP", "::")
listen_ip = System.get_env("MOBILIZON_INSTANCE_LISTEN_IP", "127.0.0.1")
listen_ip =
case listen_ip |> to_charlist() |> :inet.parse_address() do

View File

@ -1,13 +1,14 @@
version: "3"
version: "3.2"
services:
postgres:
container_name: mobilizon_db
restart: unless-stopped
image: postgis/postgis:13-3.0
image: postgis/postgis
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: mobilizon_dev
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- POSTGRES_PORT
volumes:
- pgdata:/var/lib/postgresql/data
api:
@ -17,29 +18,24 @@ services:
volumes:
- ".:/app"
ports:
- "4000:4000"
- 4000:4000
depends_on:
- postgres
environment:
MIX_ENV: "dev"
DOCKER: "true"
MOBILIZON_INSTANCE_NAME: My Mobilizon Instance
MOBILIZON_INSTANCE_HOST: mobilizon.me
MOBILIZON_INSTANCE_HOST: localhost
MOBILIZON_INSTANCE_HOST_PORT: 4000
MOBILIZON_INSTANCE_PORT: 4000
MOBILIZON_INSTANCE_EMAIL: noreply@mobilizon.me
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true"
MOBILIZON_DATABASE_PASSWORD: postgres
MOBILIZON_DATABASE_USERNAME: postgres
MOBILIZON_DATABASE_DBNAME: mobilizon_dev
MOBILIZON_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
MOBILIZON_DATABASE_USERNAME: ${POSTGRES_USER}
MOBILIZON_DATABASE_DBNAME: ${POSTGRES_DB}
MOBILIZON_DATABASE_HOST: postgres
command: >
sh -c "cd js &&
yarn install &&
cd ../ &&
mix deps.get &&
mix compile &&
mix ecto.create &&
mix ecto.migrate &&
mix phx.server"
MOBILIZON_DATABASE_PORT: ${POSTGRES_PORT}
command: sh -c "mix phx.server"
volumes:
pgdata:
.:

View File

@ -45,7 +45,7 @@ LABEL org.opencontainers.image.title="mobilizon" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE
RUN apk add --no-cache openssl ncurses-libs file postgresql-client libgcc libstdc++ imagemagick
RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick
RUN mkdir -p /app/uploads && chown nobody:nobody /app/uploads
RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon

View File

@ -1,6 +1,6 @@
{
"name": "mobilizon",
"version": "1.2.3",
"version": "1.3.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@ -29,7 +29,7 @@
"@tiptap/extension-underline": "^2.0.0-beta.7",
"@tiptap/starter-kit": "^2.0.0-beta.37",
"@tiptap/vue-2": "^2.0.0-beta.21",
"@vue/apollo-option": "^4.0.0-alpha.11",
"@vue/apollo-option": "4.0.0-alpha.11",
"apollo-absinthe-upload-link": "^1.5.0",
"blurhash": "^1.1.3",
"buefy": "^0.9.0",
@ -72,14 +72,14 @@
"@types/prosemirror-view": "^1.11.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~5.0.0-beta.2",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.2",
"@vue/cli-plugin-eslint": "~5.0.0-beta.2",
"@vue/cli-plugin-pwa": "~5.0.0-beta.2",
"@vue/cli-plugin-router": "~5.0.0-beta.2",
"@vue/cli-plugin-typescript": "~5.0.0-beta.2",
"@vue/cli-plugin-unit-jest": "~5.0.0-beta.2",
"@vue/cli-service": "~5.0.0-beta.2",
"@vue/cli-plugin-babel": "~5.0.0-beta.3",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.3",
"@vue/cli-plugin-eslint": "~5.0.0-beta.3",
"@vue/cli-plugin-pwa": "~5.0.0-beta.3",
"@vue/cli-plugin-router": "~5.0.0-beta.3",
"@vue/cli-plugin-typescript": "~5.0.0-beta.3",
"@vue/cli-plugin-unit-jest": "~5.0.0-beta.3",
"@vue/cli-service": "~5.0.0-beta.3",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.1.0",
@ -89,10 +89,11 @@
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.6.0",
"flush-promises": "^1.0.2",
"jest": "^26.6.3",
"jest-junit": "^12.0.0",
"mock-apollo-client": "^1.1.0",
"prettier": "^2.2.1",
"prettier-eslint": "^12.0.0",
"prettier-eslint": "^13.0.0",
"sass": "^1.34.1",
"sass-loader": "^12.0.0",
"ts-jest": "^26.5.3",
@ -101,5 +102,8 @@
"vue-jest": "^4.0.1",
"vue-template-compiler": "^2.6.11",
"webpack-cli": "^4.7.0"
},
"resolutions": {
"webpack": "5.44.0"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="742.753" height="742.753" viewBox="0 0 557.065 557.065"><path style="stroke:none;fill-rule:nonzero;fill:#636363;fill-opacity:1" d="M135.848 206.352c-4.887 9.359-12.715 17.152-22.098 21.996L235.066 350.14l29.25-14.825zm160.023 160.64-29.25 14.824 61.473 61.711c4.886-9.359 12.719-17.156 22.105-21.996zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#878787;fill-opacity:1" d="m436.234 254.543-68.68 34.809 5.063 32.39 77.711-39.383c-7.39-7.543-12.387-17.398-14.094-27.816zM327.68 309.559l-162.39 82.3c7.39 7.54 12.386 17.395 14.093 27.817l153.363-77.727zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#636363;fill-opacity:1" d="m275.457 106.828-78.36 152.977 23.133 23.226 82.97-161.969c-10.41-1.761-20.243-6.804-27.743-14.234zm-98.742 192.766-39.692 77.488c10.41 1.758 20.239 6.805 27.743 14.23l35.086-68.496zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#5c5c5c;fill-opacity:1" d="M113.074 228.688a51.922 51.922 0 0 1-25.808 5.398 52.012 52.012 0 0 1-4.989-.524l23.176 148.247a51.976 51.976 0 0 1 25.813-5.395c1.668.094 3.332.266 4.984.52zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#575757;fill-opacity:1" d="M179.508 420.41c.527 3.438.71 6.93.539 10.406a51.888 51.888 0 0 1-5.45 20.387l148.22 23.781a51.814 51.814 0 0 1-.54-10.406 51.852 51.852 0 0 1 5.45-20.383zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#878787;fill-opacity:1" d="m450.852 282.898-68.414 133.563c10.41 1.762 20.242 6.805 27.742 14.238l68.414-133.562c-10.41-1.762-20.242-6.805-27.742-14.239zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#ccc;fill-opacity:1" d="M357.543 93.996c-4.887 9.363-12.719 17.156-22.106 22l105.95 106.36c4.886-9.36 12.718-17.157 22.101-22zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#8f8f8f;fill-opacity:1" d="m260.84 78.473-133.93 67.875c7.39 7.539 12.383 17.394 14.094 27.812l133.93-67.875c-7.391-7.539-12.387-17.394-14.094-27.812zm74.355 37.648a52.01 52.01 0 0 1-26.238 5.61 51.5 51.5 0 0 1-4.52-.473l11.864 75.969 32.37 5.191zm-12 125.27 28.051 179.613a51.909 51.909 0 0 1 25.434-5.211c1.812.105 3.617.3 5.406.594l-26.52-169.805zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#ababab;fill-opacity:1" d="M141.098 174.73a51.84 51.84 0 0 1 .57 10.575 51.878 51.878 0 0 1-5.371 20.234l76.027 12.211 14.942-29.18zm130.304 20.926-14.945 29.184 179.633 28.85a51.828 51.828 0 0 1-.52-10.289 51.863 51.863 0 0 1 5.512-20.492zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#c2c2c2;fill-opacity:.995968" d="M358.672 72.691c-1.414 25.907-23.555 45.762-49.461 44.348-25.902-1.41-45.758-23.55-44.348-49.457 1.414-25.902 23.555-45.758 49.461-44.348 25.903 1.414 45.758 23.555 44.348 49.457zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#b3b3b3;fill-opacity:.995968" d="M534.066 248.766c-1.41 25.906-23.554 45.761-49.457 44.347-25.906-1.41-45.761-23.55-44.347-49.457 1.41-25.902 23.55-45.758 49.457-44.347 25.902 1.41 45.758 23.554 44.347 49.457zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#7d7d7d;fill-opacity:.995968" d="M420.773 469.941c-1.414 25.903-23.554 45.758-49.457 44.348-25.906-1.41-45.761-23.555-44.351-49.457 1.414-25.902 23.555-45.758 49.46-44.348 25.903 1.41 45.759 23.555 44.348 49.457zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#4a4a4a;fill-opacity:.995968" d="M175.355 430.563c-1.41 25.902-23.55 45.757-49.457 44.347-25.902-1.414-45.757-23.555-44.347-49.457 1.414-25.906 23.554-45.762 49.457-44.351 25.906 1.414 45.762 23.554 44.347 49.46zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#4d4d4d;fill-opacity:.995968" d="M136.977 185.047c-1.41 25.902-23.555 45.758-49.457 44.348-25.907-1.41-45.758-23.555-44.348-49.458 1.41-25.902 23.555-45.757 49.457-44.347 25.902 1.41 45.758 23.555 44.348 49.457zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="2799 -911 16 22"><g data-name="Artboard 1"><g data-name="Symbol 3 1"><g data-name="Group 44"><path d="M2799-911v11l8-5" data-name="Path 4"/><path d="M2799-900v11l8-6" data-name="Path 5"/><path d="M2807-905v10l8-5" data-name="Path 6"/><path fill="transparent" d="M2807-895v-10l-8 5z" data-name="Path 7"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1 @@
<svg height="100px" width="100px" fill="#000000" version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 84.922" enable-background="new 0 0 100 84.922" xml:space="preserve"><path d="M50.29,42.212"/><path d="M50.29,42.212"/><path d="M50.29,42.212"/><path d="M95.223,22.145c-5.377,3.135-8.271,4.414-13.844,4.125c-2.098-0.024-3.207-0.917-5.281-0.724 c-1.93,0.072-2.918,0.796-4.849,1.447c-2.966,0.917-4.583,1.737-7.694,2.702c-1.977,0.555-3.23,0.579-5.281,1.278 c-1.76,0.748-0.916,0.145-2.146,1.567c-1.592,1.761-1.206,3.136-2.701,6.15c-1.856,3.956-1.641,4.486-0.579,4.969 c-0.699,2.025-0.867,3.377-0.699,5.571c0.192,1.664-0.506,2.821,1.278,4.147c2.437,1.81,7.55,6.054,8.853,6.125 c1.953-0.071,7.646,0.146,10.539-1.855c0.652-0.41,0.845-0.82,0.289,0.725c-0.119,0.192-7.211,5.715-7.982,5.69 c-5.463,0.176-6.511,0.431-6.611,0.556c-0.628-0.243-4.812-5.237-7.377-3.402c-0.99,0.604-1.448,1.183-1.835,2.269 c0.338,0.699,1.931,1.81,3.4,2.726c2.123,1.352,2.315,2.772,4.438,3.281c2.58,0.577,5.33,0.191,6.969,0.408 c0.629,0.169,6.32-1.495,9.865-3.4c3.28-1.688-4.51,3.256-6.416,5.692c-5.74,1.81-4.123,1.882-6.85,2.437 c-2.532,0.578-2.412,0.434-4.148,0.988c-1.785,0.434-3.256,2.87-1.566,4.147c1.013,0.845,4.173-0.337,7.283-0.435 c1.375-0.022,6.994-2.604,8.272-3.279c3.185-1.713,5.522-4.076,7.404-5.428c2.604-1.807-2.942,4.994-5.282,7.84 c-0.675,0.918-0.988,1.109-1.832,2.291c-1.398,1.811,0.385,4.56,1.277,4.125c1.037-0.385,1.713-2.459,2.846-3.135 c0.58-0.314,2.123-2.582,3.57-4.125c1.061-1.205,1.833-1.736,2.846-2.99c1.713-2.123,2.074-3.738,3.57-6.006 c0.916-1.183,1.566-1.543,2.41-2.99c4.463-8.441,3.16-12.229,7.43-18.258c1.303-1.785,2.773-2.22,4.848-3.281 C101.566,30.031,95.175,22.169,95.223,22.145z M74.529,44.528c-1.014,3.545-0.916,3.955-2.846,5.281 c-2.34,1.785-2.461,0.434-7.838,3.425c0,0-2.22-2.315-5.717-3.425c0.023,0.049-0.941-4.486-2.846-6.006 c4.533-3.449,4.412-6.366,5.137-6.27c1.616,0.145,4.198,0.965,8.973-0.844C72.143,41.923,75.471,41.657,74.529,44.528z"/><path d="M47.3,18.863c-2.122-1.423-2.339-2.846-4.438-3.304c-2.58-0.627-5.354-0.241-6.971-0.555 c-0.627-0.072-6.318,1.592-9.84,3.425c-3.328,1.761,4.486-3.184,6.416-5.716c5.716-1.712,4.076-1.785,6.85-2.412 c2.508-0.506,2.387-0.362,4.124-0.868c1.761-0.482,3.256-2.895,1.567-4.269c-1.037-0.772-4.172,0.41-7.26,0.434 C36.35,5.696,30.729,8.277,29.451,9c-3.184,1.664-5.523,4.028-7.404,5.282c-2.629,1.906,2.942-4.872,5.282-7.694 c0.675-0.965,0.989-1.158,1.856-2.291c1.352-1.857-0.41-4.607-1.277-4.269c-1.062,0.482-1.736,2.556-3.016,3.28 c-0.458,0.266-2.002,2.533-3.424,4.125c-1.062,1.158-1.834,1.688-2.847,3.015c-1.736,2.05-2.074,3.666-3.569,5.837 c-0.916,1.302-1.592,1.64-2.412,3.135C8.179,27.813,9.457,31.6,5.212,37.533c-1.302,1.905-2.773,2.315-4.848,3.425 c-1.93,14.037,4.438,21.876,4.413,21.828c5.379-3.062,8.249-4.342,13.845-4.147c2.099,0.12,3.208,1.012,5.282,0.866 c1.93-0.119,2.918-0.844,4.848-1.422c2.967-0.988,4.582-1.81,7.549-2.727c2.123-0.604,3.377-0.627,5.428-1.422 c1.76-0.652,0.916-0.049,2.146-1.424c1.567-1.809,1.205-3.184,2.557-6.127c2.002-4.027,1.784-4.558,0.723-4.992 c0.676-2.074,0.869-3.4,0.699-5.571c-0.217-1.688,0.507-2.87-1.277-4.125c-2.437-1.881-7.55-6.126-8.828-6.15 c-1.978,0.024-7.67-0.193-10.564,1.712c-0.65,0.506-0.844,0.917-0.289-0.555c0.121-0.266,7.212-5.789,7.983-5.861 c5.059-0.108,6.332-0.315,6.573-0.429c0.5,0.013,4.804,5.243,7.417,3.42c0.965-0.651,1.447-1.23,1.857-2.267 C50.364,20.817,48.772,19.708,47.3,18.863z M28.318,35.121c2.315-1.712,2.459-0.362,7.838-3.28c0-0.073,2.219,2.243,5.717,3.28 c-0.024,0.024,0.94,4.558,2.846,6.126c-4.535,3.401-4.414,6.32-5.138,6.271c-1.617-0.193-4.197-1.014-8.972,0.725 c-2.774-5.162-6.078-4.873-5.138-7.864C26.484,36.954,26.364,36.52,28.318,35.121z"/></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -56,7 +56,7 @@ export const typePolicies: TypePolicies = {
},
Person: {
fields: {
organizedEvents: pageLimitPagination(),
organizedEvents: paginatedLimitPagination<IEvent>(),
participations: paginatedLimitPagination<IParticipant>(["eventId"]),
memberships: paginatedLimitPagination<IMember>(["group"]),
},
@ -104,6 +104,11 @@ export async function refreshAccessToken(
const refreshToken = localStorage.getItem(AUTH_REFRESH_TOKEN);
if (!refreshToken) {
console.debug("Refresh token not found");
return false;
}
console.log("Refreshing access token.");
try {
@ -118,6 +123,7 @@ export async function refreshAccessToken(
return true;
} catch (err) {
console.debug("Failed to refresh token");
return false;
}
}
@ -165,12 +171,13 @@ function doMerge<T = any>(
args: Record<string, any> | null
): Array<T> {
const merged = existing && Array.isArray(existing) ? existing.slice(0) : [];
const previous = incoming && Array.isArray(incoming) ? incoming.slice(0) : [];
let res;
if (args) {
// Assume an page of 1 if args.page omitted.
const { page = 1, limit = 10 } = args;
for (let i = 0; i < incoming.length; ++i) {
merged[(page - 1) * limit + i] = incoming[i];
for (let i = 0; i < previous.length; ++i) {
merged[(page - 1) * limit + i] = previous[i];
}
res = merged;
} else {
@ -178,7 +185,7 @@ function doMerge<T = any>(
// to receive any arguments, so you might prefer to throw an
// exception here, instead of recovering by appending incoming
// onto the existing array.
res = [...merged, ...incoming];
res = [...merged, ...previous];
// eslint-disable-next-line no-underscore-dangle
res = uniqBy(res, (elem: any) => elem.__ref);
}

View File

@ -250,7 +250,9 @@ export default class EditorComponent extends Vue {
Mention.configure(MentionOptions),
CustomImage,
Underline,
Link,
Link.configure({
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer ugc" },
}),
CharacterCount.configure({
limit: this.maxSize,
}),

View File

@ -5,6 +5,7 @@
</template>
<script lang="ts">
import { IMedia } from "@/types/media.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
@ -14,7 +15,7 @@ import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
},
})
export default class EventBanner extends Vue {
@Prop({ required: true, default: null })
@Prop({ default: null, type: Object as PropType<IMedia> })
picture!: IMedia | null;
}
</script>

View File

@ -2,7 +2,18 @@
<div>
<h2>{{ title }}</h2>
<div class="eventMetadataBlock">
<b-icon v-if="icon" :icon="icon" size="is-medium" />
<!-- Custom icons -->
<span
class="icon is-medium"
v-if="icon && icon.substring(0, 7) === 'mz:icon'"
>
<img
:src="`/img/${icon.substring(8)}_monochrome.svg`"
width="32"
height="32"
/>
</span>
<b-icon v-else-if="icon" :icon="icon" size="is-medium" />
<p :class="{ 'padding-left': icon }">
<slot></slot>
</p>
@ -36,6 +47,13 @@ div.eventMetadataBlock {
&.padding-left {
padding: 0 20px;
a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}

View File

@ -0,0 +1,140 @@
<template>
<div class="card card-content">
<div class="media">
<div class="media-left">
<img
v-if="
metadataItem.icon && metadataItem.icon.substring(0, 7) === 'mz:icon'
"
:src="`/img/${metadataItem.icon.substring(8)}_monochrome.svg`"
width="24"
height="24"
/>
<b-icon v-else-if="metadataItem.icon" :icon="metadataItem.icon" />
<b-icon v-else icon="help-circle" />
</div>
<div class="media-content">
<b>{{ metadataItem.title || metadataItem.label }}</b>
<br />
<small>
{{ metadataItem.description }}
</small>
<div
v-if="
metadataItem.type === EventMetadataType.STRING &&
metadataItem.keyType === EventMetadataKeyType.CHOICE &&
metadataItem.choices
"
>
<b-field v-for="(value, key) in metadataItem.choices" :key="key">
<b-radio v-model="metadataItemValue" :native-value="key">{{
value
}}</b-radio>
</b-field>
</div>
<b-field
v-else-if="
metadataItem.type === EventMetadataType.STRING &&
metadataItem.keyType == EventMetadataKeyType.URL
"
>
<b-input
@blur="validatePattern"
ref="urlInput"
type="url"
:pattern="
metadataItem.pattern ? metadataItem.pattern.source : undefined
"
:validation-message="$t(`This URL doesn't seem to be valid`)"
required
v-model="metadataItemValue"
:placeholder="metadataItem.placeholder"
/>
</b-field>
<b-field v-else-if="metadataItem.type === EventMetadataType.STRING">
<b-input
v-model="metadataItemValue"
:placeholder="metadataItem.placeholder"
/>
</b-field>
<b-field v-else-if="metadataItem.type === EventMetadataType.INTEGER">
<b-numberinput v-model="metadataItemValue" />
</b-field>
<b-field v-else-if="metadataItem.type === EventMetadataType.BOOLEAN">
<b-checkbox v-model="metadataItemValue">
{{
metadataItemValue === "true"
? metadataItem.choices["true"]
: metadataItem.choices["false"]
}}
</b-checkbox>
</b-field>
</div>
<b-button
icon-left="close"
@click="$emit('removeItem', metadataItem.key)"
/>
</div>
</div>
</template>
<script lang="ts">
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums";
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
@Component
export default class EventMetadataItem extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
value!: IEventMetadataDescription;
EventMetadataType = EventMetadataType;
EventMetadataKeyType = EventMetadataKeyType;
@Ref("urlInput") readonly urlInput!: any;
get metadataItem(): IEventMetadataDescription {
return this.value;
}
get metadataItemValue(): string {
return this.metadataItem.value;
}
set metadataItemValue(value: string) {
if (this.validate(value)) {
this.$emit("input", { ...this.metadataItem, value: value.toString() });
}
}
validatePattern(): void {
this.urlInput.checkHtml5Validity();
}
private validate(value: string): boolean {
if (this.metadataItem.keyType === EventMetadataKeyType.URL) {
try {
const url = new URL(value);
if (!["http:", "https:", "mailto:"].includes(url.protocol))
return false;
if (this.metadataItem.pattern) {
return value.match(this.metadataItem.pattern) !== null;
}
} catch {
return false;
}
}
return true;
}
}
</script>
<style lang="scss" scoped>
.card .media {
align-items: center;
& > button {
margin-left: 1rem;
}
}
</style>

View File

@ -0,0 +1,209 @@
<template>
<section>
<div class="mb-4">
<div v-for="(item, index) in metadata" :key="item.key" class="my-2">
<event-metadata-item
:value="metadata[index]"
@input="updateSingleMetadata"
@removeItem="removeItem"
/>
</div>
</div>
<b-field grouped :label="$t('Find or add an element')">
<b-autocomplete
expanded
v-model="search"
ref="autocomplete"
:data="filteredDataArray"
group-field="category"
group-options="items"
open-on-focus
:placeholder="$t('e.g. Accessibility, Twitch, PeerTube')"
@select="(option) => addElement(option)"
>
<template slot-scope="props">
<div class="media">
<div class="media-left">
<img
v-if="
props.option.icon &&
props.option.icon.substring(0, 7) === 'mz:icon'
"
:src="`/img/${props.option.icon.substring(8)}_monochrome.svg`"
width="24"
height="24"
/>
<b-icon v-else-if="props.option.icon" :icon="props.option.icon" />
<b-icon v-else icon="help-circle" />
</div>
<div class="media-content">
<b>{{ props.option.label }}</b>
<br />
<small>
{{ props.option.description }}
</small>
</div>
</div>
</template>
<template #empty>{{
$t("No results for {search}", { search })
}}</template>
</b-autocomplete>
<p class="control">
<b-button @click="showNewElementModal = true">
{{ $t("Add new…") }}
</b-button>
</p>
</b-field>
<b-modal has-modal-card v-model="showNewElementModal">
<div class="modal-card">
<header class="modal-card-head">
<button
type="button"
class="delete"
@click="showNewElementModal = false"
/>
</header>
<div class="modal-card-body">
<form @submit="addNewElement">
<b-field :label="$t('Element title')">
<b-input v-model="newElement.title" />
</b-field>
<b-field :label="$t('Element value')">
<b-input v-model="newElement.value" />
</b-field>
<b-button type="is-primary" native-type="submit">{{
$t("Add")
}}</b-button>
</form>
</div>
</div>
</b-modal>
</section>
</template>
<script lang="ts">
import {
IEventMetadata,
IEventMetadataDescription,
} from "@/types/event-metadata";
import cloneDeep from "lodash/cloneDeep";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import EventMetadataItem from "./EventMetadataItem.vue";
import { eventMetaDataList } from "../../services/EventMetadata";
import { EventMetadataCategories, EventMetadataType } from "@/types/enums";
type GroupedIEventMetadata = Array<{
category: string;
items: IEventMetadata[];
}>;
@Component({
components: {
EventMetadataItem,
},
})
export default class EventMetadataList extends Vue {
@Prop({ type: Array as PropType<Array<IEventMetadata>>, required: true })
value!: IEventMetadata[];
newElement = {
title: "",
value: "",
};
search = "";
data: IEventMetadataDescription[] = eventMetaDataList;
showNewElementModal = false;
get metadata(): IEventMetadata[] {
return this.value.map((val) => {
const def = this.data.find((dat) => dat.key === val.key);
return {
...def,
...val,
};
}) as any[];
}
set metadata(metadata: IEventMetadata[]) {
this.$emit("input", metadata);
}
localizedCategories: Record<EventMetadataCategories, string> = {
[EventMetadataCategories.ACCESSIBILITY]: this.$t("Accessibility") as string,
[EventMetadataCategories.LIVE]: this.$t("Live") as string,
[EventMetadataCategories.REPLAY]: this.$t("Replay") as string,
[EventMetadataCategories.TOOLS]: this.$t("Tools") as string,
[EventMetadataCategories.SOCIAL]: this.$t("Social") as string,
[EventMetadataCategories.DETAILS]: this.$t("Details") as string,
[EventMetadataCategories.BOOKING]: this.$t("Booking") as string,
[EventMetadataCategories.VIDEO_CONFERENCE]: this.$t(
"Video Conference"
) as string,
};
get filteredDataArray(): GroupedIEventMetadata {
return this.data
.filter((option) => {
return (
option.label
.toString()
.toLowerCase()
.indexOf(this.search.toLowerCase()) >= 0
);
})
.filter(({ key }) => {
return !this.metadata.map(({ key: key2 }) => key2).includes(key);
})
.reduce(
(acc: GroupedIEventMetadata, current: IEventMetadataDescription) => {
const group = acc.find(
(elem) =>
elem.category === this.localizedCategories[current.category]
);
if (group) {
group.items.push(current);
} else {
acc.push({
category: this.localizedCategories[current.category],
items: [current],
});
}
return acc;
},
[]
);
}
updateSingleMetadata(element: IEventMetadataDescription): void {
const metadataClone = cloneDeep(this.metadata);
const index = metadataClone.findIndex((elem) => elem.key === element.key);
metadataClone.splice(index, 1, element);
this.$emit("input", metadataClone);
}
removeItem(itemKey: string): void {
const metadataClone = cloneDeep(this.metadata);
const index = metadataClone.findIndex((elem) => elem.key === itemKey);
metadataClone.splice(index, 1);
this.$emit("input", metadataClone);
}
addElement(element: IEventMetadata): void {
this.metadata = [...this.metadata, element];
}
addNewElement(e: Event): void {
e.preventDefault();
this.addElement({
...this.newElement,
type: EventMetadataType.STRING,
key: `mz:plain:${(Math.random() + 1).toString(36).substring(7)}`,
});
this.showNewElementModal = false;
}
}
</script>

View File

@ -0,0 +1,450 @@
<template>
<div>
<event-metadata-block
:title="$t('Location')"
:icon="physicalAddress ? physicalAddress.poiInfos.poiIcon.icon : 'earth'"
>
<div class="address-wrapper">
<span v-if="!physicalAddress">{{ $t("No address defined") }}</span>
<div class="address" v-if="physicalAddress">
<div>
<address>
<p
class="addressDescription"
:title="physicalAddress.poiInfos.name"
>
{{ physicalAddress.poiInfos.name }}
</p>
<p class="has-text-grey-dark">
{{ physicalAddress.poiInfos.alternativeName }}
</p>
</address>
</div>
<span
class="map-show-button"
@click="showMap = !showMap"
v-if="physicalAddress.geom"
>{{ $t("Show map") }}</span
>
</div>
</div>
</event-metadata-block>
<event-metadata-block :title="$t('Date and time')" icon="calendar">
<event-full-date
:beginsOn="event.beginsOn"
:show-start-time="event.options.showStartTime"
:show-end-time="event.options.showEndTime"
:endsOn="event.endsOn"
/>
</event-metadata-block>
<event-metadata-block
class="metadata-organized-by"
:title="$t('Organized by')"
>
<popover-actor-card
:actor="event.organizerActor"
v-if="!event.attributedTo"
>
<actor-card :actor="event.organizerActor" />
</popover-actor-card>
<router-link
v-if="event.attributedTo"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(event.attributedTo),
},
}"
>
<popover-actor-card
:actor="event.attributedTo"
v-if="
!event.attributedTo || !event.options.hideOrganizerWhenGroupEvent
"
>
<actor-card :actor="event.attributedTo" />
</popover-actor-card>
</router-link>
<popover-actor-card
:actor="contact"
v-for="contact in event.contacts"
:key="contact.id"
>
<actor-card :actor="contact" />
</popover-actor-card>
</event-metadata-block>
<event-metadata-block
v-if="event.onlineAddress && urlToHostname(event.onlineAddress)"
icon="link"
:title="$t('Website')"
>
<a
target="_blank"
rel="noopener noreferrer ugc"
:href="event.onlineAddress"
:title="
$t('View page on {hostname} (in a new window)', {
hostname: urlToHostname(event.onlineAddress),
})
"
>{{ simpleURL(event.onlineAddress) }}</a
>
</event-metadata-block>
<event-metadata-block
v-for="extra in extraMetadata"
:title="extra.title || extra.label"
:icon="extra.icon"
:key="extra.key"
>
<span
v-if="
((extra.type == EventMetadataType.STRING &&
extra.keyType == EventMetadataKeyType.CHOICE) ||
extra.type === EventMetadataType.BOOLEAN) &&
extra.choices &&
extra.choices[extra.value]
"
>
{{ extra.choices[extra.value] }}
</span>
<a
v-else-if="
extra.type == EventMetadataType.STRING &&
extra.keyType == EventMetadataKeyType.URL
"
target="_blank"
rel="noopener noreferrer ugc"
:href="extra.value"
:title="
$t('View page on {hostname} (in a new window)', {
hostname: urlToHostname(extra.value),
})
"
>{{ simpleURL(extra.value) }}</a
>
<a
v-else-if="
extra.type == EventMetadataType.STRING &&
extra.keyType == EventMetadataKeyType.HANDLE
"
target="_blank"
rel="noopener noreferrer ugc"
:href="accountURL(extra)"
:title="
$t('View account on {hostname} (in a new window)', {
hostname: urlToHostname(accountURL(extra)),
})
"
>{{ extra.value }}</a
>
<span v-else>{{ extra.value }}</span>
</event-metadata-block>
<b-modal
class="map-modal"
v-if="physicalAddress && physicalAddress.geom"
:active.sync="showMap"
has-modal-card
full-screen
>
<div class="modal-card">
<header class="modal-card-head">
<button type="button" class="delete" @click="showMap = false" />
</header>
<div class="modal-card-body">
<section class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
</section>
<section class="columns is-centered map-footer">
<div class="column is-half has-text-centered">
<p class="address">
<i class="mdi mdi-map-marker"></i>
{{ physicalAddress.fullName }}
</p>
<p class="getting-there">{{ $t("Getting there") }}</p>
<div
class="buttons"
v-if="
addressLinkToRouteByCar ||
addressLinkToRouteByBike ||
addressLinkToRouteByFeet
"
>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByFeet"
:href="addressLinkToRouteByFeet"
>
<i class="mdi mdi-walk"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByBike"
:href="addressLinkToRouteByBike"
>
<i class="mdi mdi-bike"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByTransit"
:href="addressLinkToRouteByTransit"
>
<i class="mdi mdi-bus"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByCar"
:href="addressLinkToRouteByCar"
>
<i class="mdi mdi-car"></i>
</a>
</div>
</div>
</section>
</div>
</div>
</b-modal>
</div>
</template>
<script lang="ts">
import { Address } from "@/types/address.model";
import { IConfig } from "@/types/config.model";
import {
EventMetadataKeyType,
EventMetadataType,
RoutingTransportationType,
RoutingType,
} from "@/types/enums";
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import RouteName from "../../router/name";
import { usernameWithDomain } from "../../types/actor";
import EventMetadataBlock from "./EventMetadataBlock.vue";
import EventFullDate from "./EventFullDate.vue";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActorCard from "../../components/Account/ActorCard.vue";
import {
IEventMetadata,
IEventMetadataDescription,
} from "@/types/event-metadata";
import { eventMetaDataList } from "../../services/EventMetadata";
@Component({
components: {
EventMetadataBlock,
EventFullDate,
PopoverActorCard,
ActorCard,
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "../../components/Map.vue"),
},
})
export default class EventMetadataSidebar extends Vue {
@Prop({ type: Object as PropType<IEvent>, required: true }) event!: IEvent;
@Prop({ type: Object as PropType<IConfig>, required: true }) config!: IConfig;
showMap = false;
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
eventMetaDataList = eventMetaDataList;
EventMetadataType = EventMetadataType;
EventMetadataKeyType = EventMetadataKeyType;
RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
[RoutingTransportationType.FOOT]: "engine=fossgis_osrm_foot",
[RoutingTransportationType.BIKE]: "engine=fossgis_osrm_bike",
[RoutingTransportationType.TRANSIT]: null,
[RoutingTransportationType.CAR]: "engine=fossgis_osrm_car",
},
[RoutingType.GOOGLE_MAPS]: {
[RoutingTransportationType.FOOT]: "dirflg=w",
[RoutingTransportationType.BIKE]: "dirflg=b",
[RoutingTransportationType.TRANSIT]: "dirflg=r",
[RoutingTransportationType.CAR]: "driving",
},
};
get physicalAddress(): Address | null {
if (!this.event.physicalAddress) return null;
return new Address(this.event.physicalAddress);
}
get extraMetadata(): IEventMetadata[] {
return this.event.metadata.map((val) => {
const def = eventMetaDataList.find((dat) => dat.key === val.key);
return {
...def,
...val,
};
});
}
makeNavigationPath(
transportationType: RoutingTransportationType
): string | undefined {
const geometry = this.physicalAddress?.geom;
if (geometry) {
const routingType = this.config.maps.routing.type;
/**
* build urls to routing map
*/
if (!this.RoutingParamType[routingType][transportationType]) {
return;
}
const urlGeometry = geometry.split(";").reverse().join(",");
switch (routingType) {
case RoutingType.GOOGLE_MAPS:
return `https://maps.google.com/?saddr=Current+Location&daddr=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}`;
case RoutingType.OPENSTREETMAP:
default: {
const bboxX = geometry.split(";").reverse()[0];
const bboxY = geometry.split(";").reverse()[1];
return `https://www.openstreetmap.org/directions?from=&to=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}#map=14/${bboxX}/${bboxY}`;
}
}
}
}
get addressLinkToRouteByCar(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.CAR);
}
get addressLinkToRouteByBike(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.BIKE);
}
get addressLinkToRouteByFeet(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.FOOT);
}
get addressLinkToRouteByTransit(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.TRANSIT);
}
urlToHostname(url: string): string | null {
try {
return new URL(url).hostname;
} catch (e) {
return null;
}
}
simpleURL(url: string): string | null {
try {
const uri = new URL(url);
return `${this.removeWWW(uri.hostname)}${uri.pathname}${uri.search}${
uri.hash
}`;
} catch (e) {
return null;
}
}
private removeWWW(string: string): string {
return string.replace(/^www./, "");
}
accountURL(extra: IEventMetadataDescription): string | undefined {
switch (extra.key) {
case "mz:social:twitter:account": {
const handle =
extra.value[0] === "@" ? extra.value.slice(1) : extra.value;
return `https://twitter.com/${handle}`;
}
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .metadata-organized-by {
.v-popover.popover .trigger {
width: 100%;
.media-content {
width: calc(100% - 32px - 1rem);
max-width: 80vw;
p.has-text-grey-dark {
text-overflow: ellipsis;
overflow: hidden;
}
}
}
}
div.address-wrapper {
display: flex;
flex: 1;
flex-wrap: wrap;
div.address {
flex: 1;
.map-show-button {
cursor: pointer;
}
address {
font-style: normal;
flex-wrap: wrap;
display: flex;
justify-content: flex-start;
span.addressDescription {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 0 auto;
min-width: 100%;
max-width: 4rem;
overflow: hidden;
}
:not(.addressDescription) {
flex: 1;
min-width: 100%;
}
}
}
}
.map-modal {
.modal-card-head {
justify-content: flex-end;
button.delete {
margin-right: 1rem;
}
}
section.map {
height: calc(100% - 8rem);
width: calc(100% - 20px);
}
section.map-footer {
p.address {
margin: 1rem auto;
}
div.buttons {
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div class="etherpad">
<div class="etherpad-container" v-if="metadata">
<iframe
:src="`${metadata.value}?showChat=false&showLineNumbers=false`"
width="600"
height="400"
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class EtherpadIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;
}
</script>
<style lang="scss" scoped>
.etherpad {
.etherpad-container {
padding-top: 56.25%;
position: relative;
height: 0;
iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
}
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div class="jitsi-meet">
<div class="jitsi-meet-video" v-if="metadata">
<iframe
allow="camera; microphone; fullscreen; display-capture; autoplay"
:src="metadata.value"
style="height: 100%; width: 100%; border: 0px"
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class JitsiMeetIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;
}
</script>
<style lang="scss" scoped>
.jitsi-meet {
.jitsi-meet-video {
padding-top: 56.25%;
position: relative;
height: 0;
iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
}
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<div class="peertube">
<div class="peertube-video" v-if="videoDetails">
<iframe
width="100%"
height="100%"
sandbox="allow-same-origin allow-scripts allow-popups"
:src="`https://${videoDetails.host}/videos/embed/${videoDetails.uuid}`"
frameborder="0"
allowfullscreen
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class PeerTubeIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;
get videoDetails(): { host: string; uuid: string } | null {
if (this.metadata.pattern) {
const matches = this.metadata.pattern.exec(this.metadata.value);
if (matches && matches[1] && matches[2]) {
return { host: matches[1], uuid: matches[2] };
}
}
return null;
}
get origin(): string {
return window.location.hostname;
}
}
</script>
<style lang="scss" scoped>
.peertube {
.peertube-video {
padding-top: 56.25%;
position: relative;
height: 0;
iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
}
}
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div class="twitch">
<div class="twitch-video" v-if="channelName">
<iframe
:src="`https://player.twitch.tv/?channel=${channelName}&parent=${origin}&autoplay=false`"
frameborder="0"
scrolling="no"
allowfullscreen="true"
height="100%"
width="100%"
>
</iframe>
</div>
</div>
</template>
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class TwitchIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;
get channelName(): string | null {
if (this.metadata.pattern) {
const matches = this.metadata.pattern.exec(this.metadata.value);
if (matches && matches[1]) {
return matches[1];
}
}
return null;
}
get origin(): string {
return window.location.hostname;
}
}
</script>
<style lang="scss" scoped>
.twitch {
.twitch-video {
padding-top: 56.25%;
position: relative;
height: 0;
iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
}
}
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div class="youtube">
<div class="youtube-video" v-if="videoID">
<iframe
width="100%"
height="100%"
:src="`https://www.youtube.com/embed/${videoID}`"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class YouTubeIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;
get videoID(): string | null {
if (this.metadata.pattern) {
const matches = this.metadata.pattern.exec(this.metadata.value);
if (matches && matches[1]) {
return matches[1];
}
}
return null;
}
get origin(): string {
return window.location.hostname;
}
}
</script>
<style lang="scss" scoped>
.youtube {
.youtube-video {
padding-top: 56.25%;
position: relative;
height: 0;
iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
}
}
}
</style>

View File

@ -1,11 +1,14 @@
<template>
<div class="list is-hoverable">
<b-input
:placeholder="$t('Filter by profile or group name')"
v-model="actorFilter"
/>
<b-radio-button
v-model="selectedActor"
:native-value="availableActor"
class="list-item"
v-for="availableActor in actualAvailableActors"
:class="{ 'is-active': availableActor.id === selectedActor.id }"
v-for="availableActor in actualFilteredAvailableActors"
:key="availableActor.id"
>
<div class="media">
@ -61,6 +64,8 @@ export default class OrganizerPicker extends Vue {
currentActor!: IPerson;
actorFilter = "";
get selectedActor(): IActor | undefined {
if (this.value?.id) {
return this.value;
@ -103,6 +108,16 @@ export default class OrganizerPicker extends Vue {
...this.actualMemberships.map((member) => member.parent),
].filter((elem) => elem);
}
get actualFilteredAvailableActors(): IActor[] {
return this.actualAvailableActors.filter((actor) => {
return [
actor.preferredUsername.toLowerCase(),
actor.name?.toLowerCase(),
actor.domain?.toLowerCase(),
].some((match) => match?.includes(this.actorFilter.toLowerCase()));
});
}
}
</script>
<style lang="scss" scoped>

View File

@ -52,17 +52,25 @@
</header>
<section class="modal-card-body">
<div class="columns">
<div class="column">
<div class="column actor-picker">
<organizer-picker
v-model="selectedActor"
@input="relay"
:restrict-moderator-level="true"
/>
</div>
<div class="column">
<div v-if="actorMembers.length > 0">
<div class="column contact-picker">
<div v-if="isSelectedActorAGroup && actorMembers.length > 0">
<p>{{ $t("Add a contact") }}</p>
<p class="field" v-for="actor in actorMembers" :key="actor.id">
<b-input
:placeholder="$t('Filter by name')"
v-model="contactFilter"
/>
<p
class="field"
v-for="actor in filteredActorMembers"
:key="actor.id"
>
<b-checkbox v-model="actualContacts" :native-value="actor.id">
<div class="media">
<div class="media-left">
@ -77,18 +85,18 @@
</div>
<div class="media-content" v-if="actor.name">
<p class="is-4">{{ actor.name }}</p>
<p class="is-6 has-text-grey">
{{ `@${actor.preferredUsername}` }}
<p class="is-6 has-text-grey-dark">
{{ `@${usernameWithDomain(actor)}` }}
</p>
</div>
<div class="media-content" v-else>
{{ `@${actor.preferredUsername}` }}
{{ `@${usernameWithDomain(actor)}` }}
</div>
</div>
</b-checkbox>
</p>
</div>
<div v-else class="content has-text-grey has-text-centered">
<div v-else class="content has-text-grey-dark has-text-centered">
<p>{{ $t("Your profile will be shown as contact.") }}</p>
</div>
</div>
@ -167,6 +175,10 @@ export default class OrganizerPickerWrapper extends Vue {
isComponentModalActive = false;
contactFilter = "";
usernameWithDomain = usernameWithDomain;
@Prop({ type: Array, required: false, default: () => [] })
contacts!: IActor[];
members: Paginate<IMember> = { elements: [], total: 0 };
@ -224,19 +236,33 @@ export default class OrganizerPickerWrapper extends Vue {
}
get actorMembers(): IActor[] {
if (this.selectedActor?.type === ActorType.GROUP) {
if (this.isSelectedActorAGroup) {
return this.members.elements.map(({ actor }: { actor: IActor }) => actor);
}
return [];
}
get filteredActorMembers(): IActor[] {
return this.actorMembers.filter((actor) => {
return [
actor.preferredUsername.toLowerCase(),
actor.name?.toLowerCase(),
actor.domain?.toLowerCase(),
].some((match) => match?.includes(this.contactFilter.toLowerCase()));
});
}
get isSelectedActorAGroup(): boolean {
return this.selectedActor?.type === ActorType.GROUP;
}
}
</script>
<style lang="scss" scoped>
.group-picker {
.block,
.no-group,
.inline {
cursor: pointer;
.modal-card-body .columns .column {
&.actor-picker,
&.contact-picker {
overflow-y: auto;
max-height: 400px;
}
}
</style>

View File

@ -96,6 +96,7 @@ export default class Footer extends Vue {
// eslint-disable-next-line class-methods-use-this
async updateLocale(locale: string): Promise<void> {
if (locale) {
console.debug("Setting locale from footer");
await loadLanguageAsync(locale);
saveLocaleData(locale);
}

View File

@ -1,105 +0,0 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $t("Pick a group") }}</p>
</header>
<section class="modal-card-body">
<div class="list is-hoverable">
<a
class="list-item"
v-for="groupMembership in actualMemberships"
:class="{
'is-active': groupMembership.parent.id === currentGroup.id,
}"
@click="changeCurrentGroup(groupMembership.parent)"
:key="groupMembership.id"
>
<div class="media">
<img
class="media-left image is-48x48"
v-if="groupMembership.parent.avatar"
:src="groupMembership.parent.avatar.url"
alt=""
/>
<b-icon
class="media-left"
v-else
size="is-large"
icon="account-circle"
/>
<div class="media-content">
<h3>@{{ groupMembership.parent.name }}</h3>
<small>{{
`@${groupMembership.parent.preferredUsername}`
}}</small>
</div>
</div>
</a>
<a
class="list-item"
@click="changeCurrentGroup(new Group())"
v-if="currentGroup.id"
>
<h3>{{ $t("Unset group") }}</h3>
</a>
</div>
</section>
<slot name="footer" />
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { IGroup, IPerson, Group } from "@/types/actor";
import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { Paginate } from "@/types/paginate";
import { IMember } from "@/types/actor/member.model";
import { MemberRole } from "@/types/enums";
@Component({
apollo: {
groupMemberships: {
query: PERSON_MEMBERSHIPS,
variables() {
return {
id: this.identity.id,
};
},
update: (data) => data.person.memberships,
skip() {
return !this.identity.id;
},
},
},
})
export default class GroupPicker extends Vue {
@Prop() value!: IGroup;
@Prop() identity!: IPerson;
@Prop({ required: false, default: false }) restrictModeratorLevel!: boolean;
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
currentGroup: IGroup = this.value;
Group = Group;
changeCurrentGroup(group: IGroup): void {
this.currentGroup = group;
this.$emit("input", group);
}
get actualMemberships(): IMember[] {
if (this.restrictModeratorLevel) {
return this.groupMemberships.elements.filter((membership: IMember) =>
[
MemberRole.ADMINISTRATOR,
MemberRole.MODERATOR,
MemberRole.CREATOR,
].includes(membership.role)
);
}
return this.groupMemberships.elements;
}
}
</script>

View File

@ -1,132 +0,0 @@
<template>
<div class="group-picker">
<div
class="no-group box"
v-if="!currentGroup.id && groupMemberships.total > 0"
@click="isComponentModalActive = true"
>
<p class="is-4">{{ $t("Add a group") }}</p>
<p class="is-6 is-size-6 has-text-grey">
{{ $t("The event will show the group as organizer.") }}
</p>
</div>
<div
v-if="inline && currentGroup.id"
class="inline box"
@click="isComponentModalActive = true"
>
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="currentGroup.avatar">
<img
class="image is-rounded"
:src="currentGroup.avatar.url"
:alt="currentGroup.avatar.alt"
/>
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="media-content" v-if="currentGroup.name">
<p class="is-4">{{ currentGroup.name }}</p>
<p class="is-6 has-text-grey">
{{ `@${currentGroup.preferredUsername}` }}
</p>
</div>
<div class="media-content" v-else>
{{ `@${currentGroup.preferredUsername}` }}
</div>
<b-button type="is-text" @click="isComponentModalActive = true">
{{ $t("Change") }}
</b-button>
</div>
</div>
<span
v-else-if="currentGroup.id"
class="block"
@click="isComponentModalActive = true"
>
<img
class="image is-48x48"
v-if="currentGroup.avatar"
:src="currentGroup.avatar.url"
:alt="currentGroup.avatar.alt"
/>
<b-icon v-else size="is-large" icon="account-circle" />
</span>
<div v-if="groupMemberships.total === 0" class="box">
<p class="is-4">
{{ $t("This identity is not a member of any group.") }}
</p>
<p class="is-6 is-size-6 has-text-grey">
{{ $t("You need to create the group before you create an event.") }}
</p>
</div>
<b-modal :active.sync="isComponentModalActive" has-modal-card>
<group-picker
v-model="currentGroup"
:identity.sync="identity"
@input="relay"
:restrict-moderator-level="true"
/>
</b-modal>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { IMember } from "@/types/actor/member.model";
import { IGroup, IPerson } from "../../types/actor";
import GroupPicker from "./GroupPicker.vue";
import { PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { Paginate } from "../../types/paginate";
@Component({
components: { GroupPicker },
apollo: {
groupMemberships: {
query: PERSON_MEMBERSHIPS,
variables() {
return {
id: this.identity.id,
};
},
update: (data) => data.person.memberships,
skip() {
return !this.identity.id;
},
},
},
})
export default class GroupPickerWrapper extends Vue {
@Prop({ type: Object, required: true }) value!: IGroup;
@Prop({ default: true, type: Boolean }) inline!: boolean;
@Prop({ type: Object, required: true }) identity!: IPerson;
isComponentModalActive = false;
currentGroup: IGroup = this.value;
groupMemberships: Paginate<IMember> = { elements: [], total: 0 };
@Watch("value")
updateCurrentGroup(value: IGroup): void {
this.currentGroup = value;
}
relay(group: IGroup): void {
this.currentGroup = group;
this.$emit("input", group);
this.isComponentModalActive = false;
}
}
</script>
<style lang="scss" scoped>
.group-picker {
.block,
.no-group,
.inline {
cursor: pointer;
}
}
</style>

View File

@ -7,17 +7,21 @@ import { decode } from "blurhash";
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
@Component
export default class extends Vue {
export default class BlurhashImg extends Vue {
@Prop({ type: String, required: true }) hash!: string;
@Prop({ type: Number, default: 1 }) aspectRatio!: string;
@Ref("canvas") readonly canvas!: any;
mounted(): void {
const pixels = decode(this.hash, 32, 32);
const imageData = new ImageData(pixels, 32, 32);
const context = this.canvas.getContext("2d");
context.putImageData(imageData, 0, 0);
try {
const pixels = decode(this.hash, 32, 32);
const imageData = new ImageData(pixels, 32, 32);
const context = this.canvas.getContext("2d");
context.putImageData(imageData, 0, 0);
} catch (e) {
console.error(e);
}
}
}
</script>

View File

@ -9,6 +9,7 @@
</template>
<script lang="ts">
import { IMedia } from "@/types/media.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import LazyImage from "../Image/LazyImage.vue";
@ -31,7 +32,7 @@ const DEFAULT_PICTURE = {
},
})
export default class LazyImageWrapper extends Vue {
@Prop({ required: true })
@Prop({ required: false, type: Object as PropType<IMedia | null> })
picture!: IMedia | null;
get pictureOrDefault(): Partial<IMedia> {

View File

@ -7,12 +7,18 @@
:center="[lat, lon]"
@click="clickMap"
@update:zoom="updateZoom"
:options="{ zoomControl: false }"
>
<l-tile-layer
:url="config.maps.tiles.endpoint"
:attribution="attribution"
>
</l-tile-layer>
<l-control-zoom
position="topleft"
:zoomInTitle="$t('Zoom in')"
:zoomOutTitle="$t('Zoom out')"
></l-control-zoom>
<v-locatecontrol :options="{ icon: 'mdi mdi-map-marker' }" />
<l-marker
:lat-lng="[lat, lon]"
@ -34,7 +40,14 @@
import { Icon, LatLng, LeafletMouseEvent, LeafletEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { Component, Prop, Vue } from "vue-property-decorator";
import { LMap, LTileLayer, LMarker, LPopup, LIcon } from "vue2-leaflet";
import {
LMap,
LTileLayer,
LMarker,
LPopup,
LIcon,
LControlZoom,
} from "vue2-leaflet";
import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
@ -46,6 +59,7 @@ import { IConfig } from "../types/config.model";
LMarker,
LPopup,
LIcon,
LControlZoom,
"v-locatecontrol": Vue2LeafletLocateControl,
},
apollo: {

View File

@ -10,9 +10,9 @@
* to try to trigger location manually (not done ATM)
*/
import L, { DomEvent } from "leaflet";
import { DomEvent } from "leaflet";
import { findRealParent, propsBinder } from "vue2-leaflet";
import "leaflet.locatecontrol";
import Locatecontrol from "leaflet.locatecontrol";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component({
@ -37,12 +37,20 @@ export default class Vue2LeafletLocateControl extends Vue {
parentContainer: any;
mounted(): void {
this.mapObject = L.control.locate(this.options);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.mapObject = new Locatecontrol({
...this.options,
strings: { title: this.$t("Show me where I am") as string },
});
DomEvent.on(this.mapObject, this.$listeners as any);
propsBinder(this, this.mapObject, this.$props);
this.ready = true;
this.parentContainer = findRealParent(this.$parent);
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
this.$nextTick(() => {
this.$emit("ready", this.mapObject);
});
}
public locate(): void {

View File

@ -229,7 +229,7 @@ import RouteName from "../router/name";
loggedUser: {
query: USER_SETTINGS,
skip() {
return this.currentUser.isLoggedIn === false;
return !this.currentUser || this.currentUser.isLoggedIn === false;
},
},
},
@ -271,13 +271,17 @@ export default class NavBar extends Vue {
// If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow
if (this.identities.length === 0) {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
userAlreadyActivated: "true",
},
});
try {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
userAlreadyActivated: "true",
},
});
} catch (err) {
return undefined;
}
}
}
}
@ -285,6 +289,7 @@ export default class NavBar extends Vue {
@Watch("loggedUser")
setSavedLanguage(): void {
if (this.loggedUser?.locale) {
console.debug("Setting locale from navbar");
loadLanguageAsync(this.loggedUser.locale);
}
}

View File

@ -192,9 +192,6 @@ export default class ParticipationSection extends Vue {
if (this.event.draft || this.event.status === EventStatus.CANCELLED)
return false;
// Organizer can't participate
if (this.actorIsOrganizer) return false;
// If capacity is OK
if (this.eventCapacityOK) return true;

View File

@ -21,19 +21,52 @@ function formatTimeString(value: string): string {
});
}
function formatDateTimeString(value: string, showTime = true): string {
const options: DateTimeFormatOptions = {
weekday: undefined,
year: "numeric",
month: "long",
day: "numeric",
hour: undefined,
minute: undefined,
};
// TODO: These can be removed in favor of dateStyle/timeStyle when those two have sufficient support
// https://caniuse.com/mdn-javascript_builtins_intl_datetimeformat_datetimeformat_datestyle
const LONG_DATE_FORMAT_OPTIONS: DateTimeFormatOptions = {
weekday: undefined,
year: "numeric",
month: "long",
day: "numeric",
hour: undefined,
minute: undefined,
};
const LONG_TIME_FORMAT_OPTIONS: DateTimeFormatOptions = {
weekday: "long",
hour: "numeric",
minute: "numeric",
};
const SHORT_DATE_FORMAT_OPTIONS: DateTimeFormatOptions = {
weekday: undefined,
year: "numeric",
month: "short",
day: "numeric",
hour: undefined,
minute: undefined,
};
const SHORT_TIME_FORMAT_OPTIONS: DateTimeFormatOptions = {
weekday: "short",
hour: "numeric",
minute: "numeric",
};
function formatDateTimeString(
value: string,
showTime = true,
dateFormat = "long"
): string {
const isLongFormat = dateFormat === "long";
let options = isLongFormat
? LONG_DATE_FORMAT_OPTIONS
: SHORT_DATE_FORMAT_OPTIONS;
if (showTime) {
options.weekday = "long";
options.hour = "numeric";
options.minute = "numeric";
options = {
...options,
...(isLongFormat ? LONG_TIME_FORMAT_OPTIONS : SHORT_TIME_FORMAT_OPTIONS),
};
}
const format = new Intl.DateTimeFormat(locale(), options);
return format.format(parseDateTime(value));

View File

@ -117,6 +117,19 @@ export const GET_PERSON = gql`
}
`;
export const PERSON_FRAGMENT = gql`
fragment PersonFragment on Person {
id
avatar {
id
url
}
type
preferredUsername
name
}
`;
export const LIST_PROFILES = gql`
query ListProfiles(
$preferredUsername: String

View File

@ -171,6 +171,12 @@ const FULL_EVENT_FRAGMENT = gql`
options {
...EventOptions
}
metadata {
key
title
value
type
}
}
${ADDRESS_FRAGMENT}
${TAG_FRAGMENT}
@ -203,8 +209,18 @@ export const FETCH_EVENT_BASIC = gql`
`;
export const FETCH_EVENTS = gql`
query FetchEvents($orderBy: EventOrderBy, $direction: SortDirection) {
events(orderBy: $orderBy, direction: $direction) {
query FetchEvents(
$orderBy: EventOrderBy
$direction: SortDirection
$page: Int
$limit: Int
) {
events(
orderBy: $orderBy
direction: $direction
page: $page
limit: $limit
) {
total
elements {
id
@ -240,14 +256,14 @@ export const FETCH_EVENTS = gql`
domain
name
}
# attributedTo {
# avatar {
# id
# url
# },
# preferredUsername,
# name,
# },
attributedTo {
avatar {
id
url
}
preferredUsername
name
}
category
tags {
...TagFragment
@ -326,6 +342,7 @@ export const EDIT_EVENT = gql`
$physicalAddress: AddressInput
$options: EventOptionsInput
$contacts: [Contact]
$metadata: EventMetadataInput
) {
updateEvent(
eventId: $id
@ -347,6 +364,7 @@ export const EDIT_EVENT = gql`
physicalAddress: $physicalAddress
options: $options
contacts: $contacts
metadata: $metadata
) {
...FullEvent
}

View File

@ -15,12 +15,19 @@
"@{username}'s follow request was accepted": "S'ha acceptat la soŀlicitud de seguiment de @{username}",
"@{username}'s follow request was rejected": "S'ha rebutjat la soŀlicitud de seguir-te de @{username}",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Una cookie és un arxiu petit amb informació que s'envia al teu ordinador quan visites una web. Quan tornes a visitar el lloc web, la cookie fa que el lloc pugui reconèixer el teu navegador. Les cookies poden conservar preferències i altres informacions. Pots configurar el teu navegador perquè rebutgi totes les cookies. Ara bé, això podria fer que algunes funcionalitats o serveis d'algunes webs deixessin de funcionar bé. L'emmagatzematge local funciona de la mateixa manera, però permet desar més informació.",
"A discussion has been created or updated": "S'ha creat o actualitzat una discussió",
"A federated software": "Un software federat",
"A member has been updated": "S'ha actualitzat un membre",
"A member requested to join one of my groups": "Un membre ha soŀlicitat unir-se a un dels meus grups",
"A new version is available.": "Hi ha una nova versió disponible.",
"A place for your code of conduct, rules or guidelines. You can use HTML tags.": "Un lloc pel codi de conducta, normes o guies. Podeu fer servir etiquetes HTML.",
"A place to explain who you are and the things that set your instance apart. You can use HTML tags.": "Un lloc per explicar en detall qui sou i què fa diferent la vostra instància de les altres. Podeu fer servir etiquetes HTML.",
"A place to publish something to the whole world, your community or just your group members.": "Un lloc per publicar un missatge per als membres del teu grup, per a tota la comunitat, o per al món sencer.",
"A place to store links to documents or resources of any type.": "Un lloc per desar enllaços a documents o recursos de qualsevol tipus.",
"A post has been published": "Han publicat una nova entrada",
"A post has been updated": "S'ha modificat una entrada",
"A practical tool": "Una eina pràctica",
"A resource has been created or updated": "S'ha creat o modificat un recurs",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Un subtítol de reclam per la pàgina principal de la instància. Per defecte és \"Gather ⋅ Organize ⋅ Mobilize\"",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Una eina senzilla, emancipatòria i ètica per a reunir-nos, organitzar-nos i mobilitzar-nos.",
"A validation email was sent to {email}": "S'ha enviat un mail de validació a {email}",
@ -40,6 +47,7 @@
"Account": "Compte",
"Account settings": "Ajustaments de compte",
"Actions": "Accions",
"Activate browser push notifications": "Activa les notificacions de navegador",
"Activated": "Activat",
"Active": "Activa",
"Activity": "Accions",
@ -67,12 +75,21 @@
"Allow all comments": "Permet tots els comentaris",
"Allow all comments from users with accounts": "Permet comentaris de qualsevol usuària registrada",
"Allow registrations": "Permetre registres",
"An error has occured while refreshing the page.": "S'ha produït un error mentre es refrescava la pàgina.",
"An error has occured. Sorry about that. You may try to reload the page.": "S'ha produït un error, ho sentim. Prova de recarregar la pàgina.",
"An error has occurred.": "Hi ha hagut un error.",
"An ethical alternative": "Una alternativa ètica",
"An event I'm going to has been updated": "S'ha actualitzat una activitat a la qual assistiré",
"An event I'm going to has posted an announcement": "Han publicat un anunci en una activitat a la qual assistiré",
"An event I'm organizing has a new comment": "Han fet un comentari nou a una activitat que organitzo",
"An event I'm organizing has a new participation": "Una persona més participarà a una activitat que organitzo",
"An event I'm organizing has a new pending participation": "Hi ha una nova assistència pendent a una activitat que organitzo",
"An event from one of my groups has been published": "S'ha publicat una activitat en un dels meus grups",
"An event from one of my groups has been updated or deleted": "S'ha actualitzat o esborrat una activitat d'un dels meus grups",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Una instància és una versió del software de Mobilizon instaŀlada i executant-se en un servidor. Qualsevol persona amb els coneixements suficients pot engegar una instància de {mobilizon_software} o d'altres aplicacions federades, pertanyents al \"fedivers\". El nom d'aquesta instància és {instance_name}. Mobilizon és una xarxa federada de múltiples instàncies (com el correu electrònic), així que les usuàries registrades en instàncies diferents poden comunicar-se encara que no estiguin registrades a la mateixa instància.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Una \"interfície de programació d'aplicacions\" o \"API\", per les sigles en anglès, és un protocol de comunicació que permet que diversos components de software interactuïn entre ells. Per exmple, l'API de Mobilizon pot permetre que software extern es comuniqui amb Mobilizon per fer certes accions, com publicar activitats en nom teu de forma automàtica i remota.",
"And {number} comments": "I {number} comentaris",
"Announcements and mentions notifications are always sent straight away.": "Els anuncis i les mencions ja es notifiquen directament.",
"Anonymous participant": "Participant anònim",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Els participants anònims hauran de confirmar la seva participació a través del correu electrònic.",
"Anonymous participations": "Participacions anònimes",
@ -93,6 +110,7 @@
"Are you sure you want to delete this entire discussion?": "Estàs segur/a que vols esborrar tota aquesta conversa?",
"Are you sure you want to delete this event? This action cannot be reverted.": "Segur que vols esborrar aquesta activitat? Aquesta acció és irreversible.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Com que l'organitzadora de l'activitat ha triat validar manualment les soŀlicituds per participar-hi, hauràs d'esperar la seva decisió. T'arribarà un correu comunicant-te-la.",
"Ask your instance admin to {enable_feature}.": "Demana a l'administració de la instància de {enable_feature}.",
"Assigned to": "Assignat a",
"Atom feed for events and posts": "Flux Atom d'activitats i publicacions",
"Avatar": "Avatar",
@ -101,6 +119,7 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Per a validar el compte i poder entrar, has de clicar l'enllaç que t'hem enviat en el mail.",
"Begins on": "Comença a",
"Bold": "Negreta",
"Browser notifications": "Notificacions de navegador",
"By @{group}": "De @{group}",
"By @{username}": "De @{username}",
"By others": "Les d'altres",
@ -132,10 +151,10 @@
"Click to upload": "Clica per pujar",
"Close": "Deshabilita",
"Close comments for all (except for admins)": "Deshabilita els comentaris per a tothom excepte admins",
"Events nearby": "Activitats prop de tu",
"Closed": "Deshabilitats",
"Comment deleted": "S'ha esborrat el comentari",
"Comment from @{username} reported": "S'ha denunciat un comentari de @{username}",
"Comment text can't be empty": "El comentari no pot ser buit",
"Comments": "Comentaris",
"Comments are closed for everybody else.": "Els comentaris estan tancats per a tots els altres.",
"Comments have been closed.": "S'han tancat els comentaris.",
@ -148,6 +167,7 @@
"Contact": "Contacte",
"Continue editing": "Continua editant",
"Cookies and Local storage": "Cookies i emmagatzematge local",
"Copy URL to clipboard": "Copia la URL al portaretalls",
"Copy details to clipboard": "Copia els detalls al porta-retalls",
"Country": "País/estat",
"Create": "Crea",
@ -158,6 +178,7 @@
"Create a new group": "Crea un grup nou",
"Create a new identity": "Crea una nova identitat",
"Create a new list": "Crea una llista nova",
"Create a new profile": "Crea un perfil nou",
"Create a pad": "Crea un pad",
"Create a videoconference": "Crea una videoconferència",
"Create an account": "Crea un compte",
@ -167,6 +188,7 @@
"Create my event": "Crea l'activitat",
"Create my group": "Crea el grup",
"Create my profile": "Crea el perfil",
"Create new links": "Crea enllaços nous",
"Create resource": "Crea un recurs",
"Create the discussion": "Crea la discussió",
"Create to-do lists for all the tasks you need to do, assign them and set due dates.": "Crea llistes de tasques, assigna responsables i marca dates límit.",
@ -232,6 +254,7 @@
"Duplicate": "Duplica",
"Edit": "Edita",
"Edit post": "Edita la publicació",
"Edit profile {profile}": "Edita el perfil {profile}",
"Edited {ago}": "S'ha editat {ago}",
"Eg: Stockholm, Dance, Chess…": "Ex.: Vilafranca, dansa, escacs…",
"Either on the {instance} instance or on another instance.": "Ja sigui a la instància {instant} o a una altra instància.",
@ -255,15 +278,20 @@
"Error stacktrace": "Error amb traça",
"Error while changing email": "Error al canviar el correu electrònic",
"Error while communicating with the server.": "S'ha produït un error a l'hora de comunicar-se amb el servidor.",
"Error while loading the preview": "S'ha produït un error carregant la vista prèvia",
"Error while login with {provider}. Retry or login another way.": "S'ha produït un error iniciant sessió amb {provider}. Torna-ho a provar o prova un altre mètode d'inici de sessió.",
"Error while login with {provider}. This login provider doesn't exist.": "S'ha produït un error en iniciar la sessió amb {provider}. No existeix aquesta proveïdora d'identitats.",
"Error while reporting group {groupTitle}": "No s'ha pogut denunciar el grup {groupTitle}",
"Error while saving report.": "S'ha produït un error a l'hora de desar la denúncia.",
"Error while subscribing to push notifications": "S'ha produït un error en subscriure't a les notificacions automàtiques",
"Error while suspending group": "S'ha produït un error en suspendre el grup",
"Error while updating participation status inside this browser": "S'ha produït un error en actualitzar l'assistència en aquest navegador",
"Error while validating account": "S'ha produït un error a l'hora de validar el compte",
"Error while validating participation": "Error al validar la participació",
"Error while validating participation request": "S'ha produït un error en validar la soŀlicitud de participació",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "És una alternativa ètica als esdeveniments, grups i pàgines de Facebook. Mobilizon és una <b>eina dissenyada per servir-te</b>. Fin de la cita.",
"Event": "Activitat",
"Event URL": "URL de l'activitat",
"Event already passed": "L'activitat ja ha passat",
"Event cancelled": "S'ha canceŀlat l'activitat",
"Event creation": "Crear esdeveniment",
@ -275,6 +303,7 @@
"Event {eventTitle} deleted": "S'ha esborrat {eventTitle}",
"Event {eventTitle} reported": "S'ha denunciat {eventTitle}",
"Events": "Activitats",
"Events nearby": "Activitats prop de tu",
"Events tagged with {tag}": "Activitats etiquetades amb {tag}",
"Everything": "Tot",
"Ex: mobilizon.fr": "Ex.: mobilizon.fr",
@ -307,15 +336,19 @@
"Gather ⋅ Organize ⋅ Mobilize": "Trobem-nos ⋅ Organitzem-nos ⋅ Mobilitzem-nos",
"General": "General",
"General information": "Informació general",
"General settings": "Configuració general",
"Getting location": "Obtenció d'ubicació",
"Getting there": "Com arribar-hi",
"Glossary": "Glossari",
"Go": "Anar",
"Go to the event page": "Porta'm a la pàgina de l'activitat",
"Going as {name}": "Hi assisteixes com a {name}",
"Group": "Grup",
"Group Followers": "Seguidores del grup",
"Group List": "Llista del grup",
"Group Members": "Membres del grup",
"Group URL": "URL del grup",
"Group activity": "Accions de grups",
"Group address": "Adreça del grup",
"Group display name": "Nom per mostrar del grup",
"Group full name": "Nom llarg del grup",
@ -343,8 +376,11 @@
"I participate": "Participo",
"I want to allow people to participate without an account.": "Vull permetre que tothom. participi sense un compte.",
"I want to approve every participation request": "Vull aprovar cada soŀlicitud de participació",
"I've been mentionned in a comment under an event": "M'han esmentat en un comentari d'una activitat",
"I've been mentionned in a group discussion": "M'han esmentat en una discussió de grup",
"ICS feed for events": "Calendari ICS de les activitats",
"ICS/WebCal Feed": "Calendari ICS/WebCal",
"Identities": "Identitats",
"Identity {displayName} created": "S'ha creat la identitat {displayName}",
"Identity {displayName} deleted": "S'ha esborrat la identitat {displayName}",
"Identity {displayName} updated": "S'ha actualitzat la identitat {displayName}",
@ -354,6 +390,7 @@
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Si et pregunten per la teva identitat federada, es forma amb un nom d'usuària i el nom de la instància. Per exemple, la identitat federada del teu primer perfil és:",
"If you have opted for manual validation of participants, Mobilizon will send you an email to inform you of new participations to be processed. You can choose the frequency of these notifications below.": "Si has optat per validar manualment les participants, Mobilizon t'enviarà un correu per informar-te de les soŀlicituds per participar. Pots triar la freqüència màxima amb què t'arribaran aquí a sota.",
"If you want, you may send a message to the event organizer here.": "Si voleu, aquí podeu enviar un missatge a l'organitzador d'esdeveniments.",
"Ignore": "Ignora",
"Impossible to login, your email or password seems incorrect.": "No s'ha pogut iniciar la sessió, el mail o contrasenya semblen incorrectes.",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "En el context següent, una aplicació és una peça de software, bé proporcionada per l'equip de Mobilizon, bé per altri, i es fa servir per interactuar amb la teva instància.",
"In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "Mentrestant, tingues en compte que el software no està acabat, encara. Més info {onBlog}.",
@ -373,12 +410,14 @@
"Instance Terms URL": "URL de les condicions de la instància",
"Instance administrator": "Administradora de la instància",
"Instance configuration": "Configuració de la instància",
"Instance feeds": "Fluxos d'instàncies",
"Instance languages": "Llengües de la instància",
"Instance rules": "Normes de la instància",
"Instance settings": "Configuracions de la instància",
"Instances": "Instàncies",
"Instances following you": "Instàncies que us segueixen",
"Instances you follow": "Instancies que seguiu",
"Interact with a remote content": "Interactua amb contingut remot",
"Invite a new member": "Convida algú",
"Invite member": "Convida",
"Invited": "Convidat/da",
@ -386,6 +425,7 @@
"Italic": "Cursiva",
"Join <b>{instance}</b>, a Mobilizon instance": "Uneix-te a <b>{instance}</b>, una instància de Mobilizon",
"Join group": "Suma't al grup",
"Join group {group}": "Afegeix-te al grup {group}",
"Join {instance}, a Mobilizon instance": "Uneix-te a {instance}, una instància de Mobilizon",
"Keep the entire conversation about a specific topic together on a single page.": "Reserva una pàgina per la conversa d'un tema específic.",
"Key words": "Paraules clau",
@ -430,6 +470,7 @@
"Mark as resolved": "Marca com resolta",
"Member": "Membre",
"Members": "Membres",
"Mentions": "Mencions",
"Message": "Missatge",
"Mobilizon": "Mobilizon",
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon és una xarxa federada. Podeu interactuar amb aquest esdeveniment des dun servidor diferent.",
@ -449,6 +490,7 @@
"Moderator": "Moderadora",
"Move": "Mou",
"Move \"{resourceName}\"": "Mou \"{resourceName}\"",
"Move resource to the root folder": "Mou el recurs a la carpeta arrel",
"Move resource to {folder}": "Mou el recurs a {folder}",
"My account": "El meu compte",
"My events": "Les meves activitats",
@ -463,6 +505,7 @@
"New members": "Nous membres",
"New note": "Nota nova",
"New password": "Contrasenya nova",
"New post": "Entrada nova",
"New profile": "Nou perfil",
"Next": "Següent",
"Next month": "El mes que ve",
@ -478,6 +521,7 @@
"No events found": "No s'ha trobat cap esdeveniment",
"No follower matches the filters": "No hi ha cap seguidor que hi coincideixi",
"No group found": "No s'ha trobat cap grup",
"No group matches the filters": "No hi ha cap grup que coincideixi amb els filtres",
"No groups found": "No s'han trobat grups",
"No information": "No n'hi ha informació",
"No instance follows your instance yet.": "Encara no hi ha cap instància que segueixi la teva.",
@ -486,11 +530,16 @@
"No instance to remove|Remove instance|Remove {number} instances": "No hi ha cap instància per esborrar|Esborra la instància|Esborra les {number} instàncies",
"No languages found": "No s'ha trobat cap llengua",
"No member matches the filters": "No hi ha cap membre que coincideixi amb els filtres especificats",
"No members found": "No se n'han trobat membres",
"No memberships found": "No se n'han trobat membres",
"No message": "Sense missatges",
"No moderation logs yet": "Encara no hi ha registres de moderació",
"No more activity to display.": "No hi ha res més a mostrar.",
"No notification settings yet": "Encara no hi ha configuració de les modificacions",
"No one is participating|One person participating|{going} people participating": "Ningú hi assistirà|Una persona hi participarà|HI participaran {going} persones",
"No open reports yet": "No hi ha cap denúncia oberta",
"No organized events found": "No s'han trobat activitats organitzades",
"No organized events listed": "",
"No participant matches the filters": "Cap participant coincideix amb els filtres",
"No participant to approve|Approve participant|Approve {number} participants": "Cap participant per aprovar|Aprova la participant|Aprova {number} participants",
"No participant to reject|Reject participant|Reject {number} participants": "Cap participant per rebutjar|Rebutja la participant|Rebutja {number} participants",
@ -513,7 +562,10 @@
"Nothing to see here": "Res d'interessant",
"Notification before the event": "Notificació abans de l'activitat",
"Notification on the day of the event": "Notificació al dia de l'activitat",
"Notification settings": "Opcions de notificacions",
"Notifications": "Notificacions",
"Notifications for manually approved participations to an event": "Notificacions per participacions aprovades manualment",
"Notify participants": "Notifica les participants",
"Now, create your first profile:": "Ara crea't el teu primer perfil:",
"Number of places": "Nombre de places",
"OK": "OK",
@ -523,6 +575,7 @@
"On {date} from {startTime} to {endTime}": "A {date} de {startTime} a {endTime}",
"On {date} starting at {startTime}": "A {date} i comença a {startTime}",
"On {instance}": "A {instance}",
"On {instance} and other federated instances": "A {instance} i a altres instàncies federades",
"One person is going": "Ningú s'hi ha apuntat|S'hi ha apuntat 1 persona|S'hi han apuntat {approved} persones",
"Only accessible through link": "Només accessible amb un enllaç",
"Only accessible through link (private)": "Només accessible mitjançant un enllaç (privat)",
@ -539,6 +592,7 @@
"Or": "O",
"Organized": "Organitzat",
"Organized by": "Organitzat per",
"Organized by you": "Organitzat per tu",
"Organized by {name}": "Organitzat per {name}",
"Organizer": "Organitzadora",
"Organizer notifications": "Notificacions d'organitzadora",
@ -568,6 +622,7 @@
"Password reset": "Restabliment de contrasenya",
"Past events": "Activitats passades",
"Pending": "Pendent",
"Personal feeds": "Fluxos personals",
"Pick": "Tria",
"Pick a group": "Escull un grup",
"Pick a profile or a group": "Tria un perfil o un grup",
@ -595,6 +650,8 @@
"Privacy policy": "Política de privacitat",
"Private event": "Activitat privada",
"Private feeds": "Fluxos privats",
"Profile": "Perfil",
"Profile feeds": "Fluxos d'informació del perfil",
"Profiles": "Perfils",
"Profiles and federation": "Perfils i federació",
"Promote": "Ascendeix",
@ -605,18 +662,22 @@
"Public feeds": "Fluxos públics",
"Public iCal Feed": "Flux iCal públic",
"Public page": "Pàgina pública",
"Public preview": "Previsualització pública",
"Publication date": "Data de publicació",
"Publish": "Publica",
"Published events": "Activitats publicades",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Activitats amb <b>{comments}</b> comentaris i <b>{participations}</b> participacions confirmades",
"Push": "Notificacions automàtiques",
"RSS/Atom Feed": "Flux RSS/Atom",
"Radius": "Radi",
"Read Framasofts statement of intent on the Framablog": "Llegeix la declaració d'intencions de Framasoft al seu blog Framablog",
"Recap every week": "Avisa'm cada setmana",
"Receive one email for each activity": "Un correu per cada novetat",
"Receive one email per request": "Un correu per soŀlicitud",
"Redirecting to content…": "S'està redirigint el navegador al contingut…",
"Redirecting to event…": "S'està redirigint a l'activitat…",
"Refresh profile": "Actualitza el perfil",
"Regenerate new links": "Regenera els enllaços",
"Region": "Regió",
"Register": "Registra't",
"Register an account on Mobilizon!": "Crea un compte a Mobilizon!",
@ -675,15 +736,19 @@
"Select a radius": "Tria un radi de distància",
"Select a timezone": "Tria un fus horari",
"Select languages": "Tria les llengües",
"Select the activities for which you wish to receive an email or a push notification.": "Tria les activitats de les quals vols rebre'n avisos per correu o notificacions automàtiques.",
"Send email": "Envia correu",
"Send me an email to reset my password": "Envia'm un correu per restablir la meva contrasenya",
"Send me the confirmation email once again": "Envia'm el correu de confirmació un altre cop",
"Send notification e-mails": "Envia correus de notificació",
"Send the confirmation email again": "Reenvia el correu de confirmació",
"Send the report": "Envia la denúncia",
"Set an URL to a page with your own privacy policy.": "Configura un enllaç a una pàgina amb la vostra política de privacitat.",
"Set an URL to a page with your own terms.": "Estableix la URL d'una pàgina amb les vostres condicions.",
"Settings": "Opcions",
"Share": "Compartir",
"Share this event": "Comparteix aquesta activitat",
"Share this group": "Comparteix aquest grup",
"Short bio": "Autodescripció curta",
"Show map": "Mostra el mapa",
"Show remaining number of places": "Mostra el nombre de places disponibles",
@ -722,6 +787,7 @@
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "L'organització de l'activitat aprova les soŀlicituds manualment. Com que vols participar-hi sense compte, et demanem que esbossis per què vols participar-hi.",
"The event title will be ellipsed.": "S'abreujarà el títol de l'activitat.",
"The event will show as attributed to this group.": "L'activitat es mostrarà atribuïda an aquest grup.",
"The event will show as attributed to this profile.": "L'activitat apareixerà atribuïda an aquest perfil.",
"The event will show as attributed to your personal profile.": "L'activitat es mostrarà atribuïda al teu perfil personal.",
"The event will show the group as organizer.": "El grup apareixerà com a organitzador de l'activitat.",
"The event {event} was created by {profile}.": "{profile} ha creat {event}.",
@ -745,6 +811,7 @@
"The post {post} was deleted by {profile}.": "{profile} ha esborrat l'entrada {post}.",
"The post {post} was updated by {profile}.": "{profile} ha actualitzat l'entrada {post}.",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "S'enviarà la denúncia a les persones moderadores de la teva instància. Pots explicar aquí a sota per què denuncies aquest contingut.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "La imatge que heu seleccionat és massa gran. Ha de ser més petita que {size}.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Els detalls tècnics de l'error solen ajudar l'equip de desenvolupament a resoldre el problema. Afegeix-nos als comentaris, sisplau.",
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.": "El compte al qual intentes accedir encara no està confirmat. Comprova el teu correu a la safata d'entrada i, si cal, a la de brossa.",
"The {default_privacy_policy} will be used. They will be translated in the user's language.": "Es farà servir la {default_privacy_policy}. Es presentarà traduïda a la llengua de cada usària.",
@ -754,16 +821,20 @@
"There will be no way to recover your data.": "No hi haurà cap manera de recuperar les teves dades.",
"There's no discussions yet": "No hi ha cap discussió",
"These events may interest you": "Pot ser que t'interessin aquestes activitats",
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Aquests fluxos porten informació per a les creadores o participants d'una activitat. És millor mantenir-los privats. Pots trobar fluxos de perfil en la pàgina d'edició de cada perfil.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Aquests fluxos porten informació per a les creadores o participants d'una activitat. És millor mantenir-los privats. Pots trobar fluxos per cada perfil teu en les opcions de notificacions.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Aquesta instància de Mobilizon i l'organització d'aquesta activitat permeten la participació anònima, però demanen una validació per correu.",
"This URL is not supported": "Aquesta URL no és compatible",
"This event has been cancelled.": "Aquesta activitat ha estat canceŀlada.",
"This event is accessible only through it's link. Be careful where you post this link.": "Aquesta activitat només és accessible a través d'aquest enllaç. Vigila com el comparteixes.",
"This group doesn't have a description yet.": "El grup encara no té descripció.",
"This group is accessible only through it's link. Be careful where you post this link.": "Aquest grup només és accessible a través d'aquest enllaç. Tingue-ho en compte a l'hora de compartir-lo.",
"This group is invite-only": "Aquest grup funciona per invitació",
"This identifier is unique to your profile. It allows others to find you.": "Aquest identificador és únic per al teu perfil. Permet als altres trobar-te.",
"This identity is not a member of any group.": "Aquesta identitat no és membre de cap grup.",
"This information is saved only on your computer. Click for details": "Aquesta informació només es desa al teu ordinador. Fes clic per més detalls",
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "Aquesta instaŀlació (o \"instància\") es pot {interconnect} fàcilment, gràcies al {protocol}.",
"This instance hasn't got push notifications enabled.": "Aquesta instància no és compatible amb les notificacions automàtiques.",
"This instance isn't opened to registrations, but you can register on other instances.": "Aquesta instància no té el registre obert, però en pots buscar una altra.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Aquesta instància, <b>{instanceName} ({domain})</b>, allotja el teu perfil. Recorda'n el nom, és important.",
"This is a demonstration site to test Mobilizon.": "Lloc web de prova de Mobilizon.",
@ -790,14 +861,18 @@
"Today": "Avui",
"Tomorrow": "Demà",
"Transfer to {outsideDomain}": "Transfereix a {outsideDomain}",
"Triggered profile refreshment": "S'ha activat l'actualització de perfil",
"Type": "Escriu",
"Type or select a date…": "Escriu o tria una data…",
"URL": "URL",
"URL copied to clipboard": "S'ha copiat la URL al portapapers",
"Unable to copy to clipboard": "No s'ha pogut copiar al porta-retalls",
"Unable to create the group. One of the pictures may be too heavy.": "No s'ha pogut crear el grup. Una de les imatges és massa grossa.",
"Unable to create the profile. The avatar picture may be too heavy.": "No s'ha pogut crear el perfil. La imatge d'avatar és massa grossa.",
"Unable to detect timezone.": "No s'ha pogut detectar el fus horari.",
"Unable to load event for participation. The error details are provided below:": "No s'ha pogut carregar l'activitat. Els detalls de l'error es detallen a sota:",
"Unable to save your participation in this browser.": "No s'ha pogut desar al navegador la teva assistència.",
"Unable to update the profile. The avatar picture may be too heavy.": "No s'ha pogut actualitzar el perfil. La imatge d'avatar és massa grossa.",
"Unfortunately, this instance isn't opened to registrations": "Malauradament, aquesta instància no està oberta a nous comptes",
"Unfortunately, your participation request was rejected by the organizers.": "Malauradament, l'organització de l'activitat ha rebutjat la teva soŀlicitud de participació.",
"Unknown": "Desconegut",
@ -806,6 +881,7 @@
"Unknown value for the openness setting.": "Nivell d'obertura del grup desconegut.",
"Unsaved changes": "Canvis sense desar",
"Unset group": "Desmarca el grup",
"Unsubscribe to browser push notifications": "Canceŀla la subscripció a les notificacions de navegador",
"Unsuspend": "Aprova",
"Upcoming": "Properament",
"Upcoming events": "Activitats properes en temps",
@ -818,6 +894,7 @@
"Uploaded media size": "Mida dels arxius pujats",
"Use my location": "Agafa la meva ubicació",
"User": "Usuària",
"User settings": "Configuració personal",
"Username": "Nom d'usuària",
"Users": "Usuàries",
"View a reply": "|Mostra la resposta|Mostra les {totalReplies} resposta",
@ -836,6 +913,7 @@
"Waiting for organization team approval.": "Pendent de l'aprovació de l'equip d'organització.",
"Waiting list": "Llista d'espera",
"Warning": "Alerta",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "No hem pogut desar la informació d'assistència en aquest navegador. Això sí, la confirmació d'assistència sí que ha arribat al servidor. És només que el teu navegador no ho reflectirà bé.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Les vostres valoracions ens ajuden a millorar el software. Per informar-nos d'aquest problema, tens dues possibilitats (requereixen un compte):",
"We just sent an email to {email}": "S'acaba d'enviar un correu a {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Fem servir el teu fus horari perquè les notificacions de les activitats t'arribin a temps.",
@ -847,6 +925,7 @@
"We'll use your timezone settings to send a recap of the morning of the event.": "Farem servir el fus horari per enviar un recordatori al matí de l'activitat.",
"Website": "Lloc web",
"Website / URL": "Web / URL",
"Weekly email summary": "Correu de resum setmanal",
"Welcome back {username}!": "Bentornat/da {username}!",
"Welcome back!": "Bentornat/da!",
"Welcome on your administration panel": "Benvingut/da al tauler d'administració",
@ -870,6 +949,7 @@
"You are already logged-in.": "Ja tens una sessió iniciada.",
"You are not an administrator for this group.": "No ets administrador/a d'aquest grup.",
"You are not part of any group.": "No formes de cap grup.",
"You are offline": "Estàs desconnectat/da",
"You are participating in this event anonymously": "Estàs registrada anònimament com a participant",
"You are participating in this event anonymously but didn't confirm participation": "Figures com a participant anònima d'aquesta activitat però no has confirmat la participació",
"You can add tags by hitting the Enter key or by adding a comma": "Pots afegir etiquetes prement Enter o afegint una coma",
@ -878,6 +958,8 @@
"You can try another search term or drag and drop the marker on the map": "Pots provar amb altres paraules de cerca o arrossegar l'indicador al mapa",
"You can't change your password because you are registered through {provider}.": "No pots canviar la teva contrasenya perquè estàs registrat/da amb {provider}.",
"You can't remove your last identity.": "No pots esborrar la teva única identitat.",
"You can't use push notifications in this browser.": "No pots rebre notificacions automàtiques en aquest navegador.",
"You changed your email or password": "Has canviat el teu mail o contrasenya",
"You created the discussion {discussion}.": "Has obert la discussió {discussion}.",
"You created the event {event}.": "Has creat l'activitat {event}.",
"You created the folder {resource}.": "Has creat la carpeta {resource}.",
@ -907,18 +989,21 @@
"You may clear all participation information for this device with the buttons below.": "Pots esborrar tota la informació d'assistències que hi ha en aquest dispositiu amb els botons de sota.",
"You may now close this window, or {return_to_event}.": "Ja pots tancar aquesta finestra o {return_to_event}.",
"You may now close this window.": "Ja pots tancar aquesta finestra.",
"You may show some members as contacts.": "Pots mostrar-ne alguns membres com a contactes.",
"You moved the folder {resource} into {new_path}.": "Has mogut la carpeta {resource} a {new_path}.",
"You moved the folder {resource} to the root folder.": "Has mogut la carpeta {recurs} a la carpeta arrel.",
"You moved the resource {resource} into {new_path}.": "Has mogut el recurs {resource} a {new_path}.",
"You moved the resource {resource} to the root folder.": "Has mogut el recurs {resource} a la carpeta arrel.",
"You need to create the group before you create an event.": "Has de tenir un grup per poder crear una activitat.",
"You need to login.": "Has d'iniciar sessió.",
"You posted a comment on the event {event}.": "Has comentat l'activitat {event}.",
"You promoted the member {member} to an unknown role.": "Has atorgat alguns poders a {member}.",
"You promoted {member} to administrator.": "Has atorgat poders d'administració a {member}.",
"You promoted {member} to moderator.": "Has atorgat poders de moderació a {member}.",
"You renamed the discussion from {old_discussion} to {discussion}.": "Has canviat el nom de discussió de {old_discussion} a {discussion}.",
"You renamed the folder from {old_resource_title} to {resource}.": "Has canviat el nom de la carpeta de {old_resource_title} a {resource}.",
"You renamed the resource from {old_resource_title} to {resource}.": "Has canviat el nom del recurs de {old_resource_title} a {resource}.",
"You replied to a comment on the event {event}.": "Has respost a un comentari a l'activitat {event}.",
"You replied to the discussion {discussion}.": "Has obert la discussió {discussion}.",
"You requested to join the group.": "Has soŀlicitat afegir-te al grup.",
"You updated the event {event}.": "Has actualitzat l'activitat {event}.",
@ -936,6 +1021,7 @@
"You will find here all the events you have created or of which you are a participant.": "Aquí hi trobaràs totes les activitats que hagis creat o a les quals estiguis apuntat/da.",
"You wish to participate to the following event": "Vols participar en l'activitat següent",
"You'll get a weekly recap every Monday for upcoming events, if you have any.": "Rebràs un recordatori cada dilluns que tinguis activitats properes planificades.",
"You'll need to change the URLs where there were previously entered.": "Hauràs d'actualitzar les URL allà on les haguessis introduïdes.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Hauràs de fer arribar l'enllaç del grup a les persones que vulguis que accedeixin al perfil del grup. No es podrà trobar en la cerca de Mobilizon ni als motors de cerca generals.",
"You'll receive a confirmation email.": "Rebràs un correu de confirmació.",
"Your account has been successfully deleted": "S'ha esborrat el teu compte",
@ -981,6 +1067,7 @@
"default Mobilizon privacy policy": "política de privacitat per defecte de Mobilizon",
"default Mobilizon terms": "condicions de Mobilizon per defecte",
"e.g. 10 Rue Jangot": "ex.: 13 Rue del Percebe",
"enable the feature": "activa la funcionalitat",
"explore the events": "explora les activitats",
"explore the groups": "explora els grups",
"firstDayOfWeek": "0",
@ -1001,10 +1088,13 @@
"{approved} / {total} seats": "{approved} / {total} places",
"{available}/{capacity} available places": "Places esgotades|Hi ha {available}/{capacity} places disponibles",
"{count} km": "{count} km",
"{count} members": "Cap membre|Un membre|{count} membres",
"{count} participants": "Cap participant per ara|Un/a participant|{count} participants",
"{count} requests waiting": "{count} soŀlicituds pendents",
"{count} team members": "{count} membres de l'equip",
"{folder} - Resources": "{folder} - Recursos",
"{group} activity timeline": "Cronologia de les accions de {group}",
"{group} events": "Activitats de {group}",
"{group}'s events": "Activitats de {group}",
"{instanceName} is an instance of the {mobilizon} software.": "{instanceName} és una instància del software {mobilizon}.",
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} és una instància de {mobilizon_link}, una aplicació de software lliure construïda per una comunitat.",
@ -1016,13 +1106,19 @@
"{moderator} added a note on {report}": "{moderator} ha afegit una nota a {report}",
"{moderator} closed {report}": "{moderator} ha tancat {report}",
"{moderator} deleted an event named \"{title}\"": "{moderator} ha esborrat l'activitat \"{title}\"",
"{moderator} has deleted a comment from {author}": "{moderator} ha esborrat un comentari de {author}",
"{moderator} has deleted a comment from {author} under the event {event}": "{moderator} ha eliminat un comentari de {author} en l'activitat {event}",
"{moderator} has deleted user {user}": "{moderator} ha esborrat l'usuari/a {user}",
"{moderator} has done an unknown action": "{moderator} ha fet alguna acció",
"{moderator} has unsuspended group {profile}": "{moderator} ha aixecat la suspensió al grup {profile}",
"{moderator} has unsuspended profile {profile}": "{moderator} ha aixecat la suspensió a {profile}",
"{moderator} marked {report} as resolved": "{moderator} ha resolt {report}",
"{moderator} reopened {report}": "{moderator} ha reobert {report}",
"{moderator} suspended group {profile}": "{moderator} ha suspès el grup {profile}",
"{moderator} suspended profile {profile}": "{moderator} ha suspès el perfil {profile}",
"{nb} km": "{nb} km",
"{number} members": "{number} membres",
"{number} memberships": "{number} membres",
"{number} organized events": "No hi ha activitats organitzades|Hi ha una activitat organitzada|Hi ha {number} activitats organitzades",
"{number} participations": "Sense participants|Un/a participant|{number} participants",
"{number} posts": "Sense publicacions|Una publicació|{number} publicacions",
@ -1045,6 +1141,7 @@
"{profile} moved the folder {resource} to the root folder.": "{profile} ha mogut la carpeta {recurs} a la carpeta arrel.",
"{profile} moved the resource {resource} into {new_path}.": "{profile} ha mogut el recurs {resource} a {new_path}.",
"{profile} moved the resource {resource} to the root folder.": "{profile} ha mogut el recurs {resource} a la carpeta arrel.",
"{profile} posted a comment on the event {event}.": "{profile} ha comentat l'activitat {event}.",
"{profile} promoted {member} to administrator.": "{profile} ha atorgat poders d'administració a {member}.",
"{profile} promoted {member} to an unknown role.": "{profile} ha atorgat alguns poders a {member}.",
"{profile} promoted {member} to moderator.": "{profile} ha atorgat poders de moderació a {member}.",
@ -1052,6 +1149,7 @@
"{profile} renamed the discussion from {old_discussion} to {discussion}.": "{profile} ha canviat el nom de la discussió de {old_discussion} a {discussion}.",
"{profile} renamed the folder from {old_resource_title} to {resource}.": "{profile} ha canviat el nom de la carpeta de {old_resource_title} a {resource}.",
"{profile} renamed the resource from {old_resource_title} to {resource}.": "{profile} ha canviat el nom del recurs de {old_resource_title} a {resource}.",
"{profile} replied to a comment on the event {event}.": "{profile} ha respost a un comentari de l'activitat {event}.",
"{profile} replied to the discussion {discussion}.": "{profile} ha respost a la discussió {discussion}.",
"{profile} updated the group {group}.": "{profile} ha actualitzat el grup {group}.",
"{profile} updated the member {member}.": "{profile} ha actualitzat el/la membre {member}.",

View File

@ -4,7 +4,7 @@
"(this folder)": "(dieser Ordner)",
"(this link)": "(dieser Link)",
"+ Add a resource": "+ Füge eine Ressource hinzu",
"+ Create an event": "+ Erstelle eine Event",
"+ Create an event": "+ Erstelle eine Veranstaltung",
"+ Post a public message": "+ Schreibe eine öffentliche Nachricht",
"+ Start a discussion": "+ Starte eine Diskussion",
"<b>Please do not use it in any real way.</b>": "<b>Bitte benutze diese Seite nicht für tatsächliche Veranstaltungsplanung.</b>",
@ -16,12 +16,18 @@
"@{username}'s follow request was rejected": "@{username}'s Folgeanfrage wurde zurückgewiesen",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Ein Cookie ist eine kleine Datei mit Informationen, die an Ihren Computer gesendet wird, wenn Sie eine Website besuchen. Wenn Sie die Website erneut besuchen, ermöglicht das Cookie dieser Website, Ihren Browser zu erkennen. Cookies können Benutzereinstellungen und andere Informationen speichern. Sie können Ihren Browser so konfigurieren, dass er alle Cookies ablehnt. Dies kann jedoch dazu führen, dass einige Funktionen oder Dienste der Website nur eingeschränkt funktionieren. Die lokale Speicherung funktioniert auf die gleiche Weise, ermöglicht es Ihnen jedoch, mehr Daten zu speichern.",
"A cookie is a small file containing informations that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows to store more data.": "Ein Cookie ist eine kleine Datei, die auf deinen Computer übertragen wird, wenn du eine Webseite aufrufst. Sie enthält Informationen, die es der Webseite ermöglichen, deinen Computer beim nächsten Besuch wiederzuerkennen. Cookies können auch genutzt werden, um nutzerspezifische Einstellungen oder andere Informationen zu speichern. Du kannst deinen Browser so einstellen, dass er alle Cookies ablehnt oder diese beim Schließen des Browsers löscht. Dies kann jedoch dazu führen, das einige Funktionalitäten von Webseiten oder Diensten nicht mehr vollständig funktionieren. Lokale Website-Daten erfüllen den selben Zweck, erlauben es einer Webseite aber, größere Datenmengen auf deinem Computer zu speichern.",
"A discussion has been created or updated": "Eine Diskussion wurde erstellt oder aktualisiert",
"A federated software": "Eine federierte Software",
"A member has been updated": "Ein Mitglied wurde aktualisiert",
"A member requested to join one of my groups": "Ein Mitglied möchte einer meiner Gruppen beitreten",
"A place for your code of conduct, rules or guidelines. You can use HTML tags.": "Hier ist Platz für Ihren Code of Conduct, Regeln und Vorgaben. Sie können HTML-Tags verwenden.",
"A place to explain who you are and the things that set your instance apart. You can use HTML tags.": "Hier können Sie beschreiben, wer Sie sind und was Ihre Instanz besonders macht. Sie können HTML-Tags verwenden.",
"A place to publish something to the whole world, your community or just your group members.": "Ein Ort, an dem Sie etwas für die ganze Welt, Ihre Community oder nur für Ihre Gruppenmitglieder veröffentlichen können.",
"A place to store links to documents or resources of any type.": "Ein Ort um Links zu Dokumenten oder Ressourcen jeden Typs zu speichern.",
"A post has been published": "Ein Beitrag wurde veröffentlicht",
"A post has been updated": "Ein Beitrag wurde aktualisiert",
"A practical tool": "Ein praktisches Werkzeug",
"A resource has been created or updated": "Eine Ressource wurde erstellt oder aktualisiert",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Eine kurze Tagline für Ihre Instanz-Homepage. Standardmäßig steht dort: \"Sammeln ⋅ Organisieren ⋅ Mobilisieren\"",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Ein benutzerfreundliches, emanzipatorisches und ethisches Instrument zum Sammeln, Organisieren und Mobilisieren.",
"A validation email was sent to {email}": "Es wurde eine Bestätigungs-Mail an {email} gesendet",
@ -72,6 +78,13 @@
"An error has occured. Sorry about that. You may try to reload the page.": "Ein Fehler ist aufgetreten. Bitte entschuldige. Du kannst versuchen die Seite neu zu laden.",
"An error has occurred.": "Ein Fehler ist aufgetreten.",
"An ethical alternative": "Eine ethische Alternative",
"An event I'm going to has been updated": "Eine Veranstaltung, an der ich teilnehme, wurde aktualisiert",
"An event I'm going to has posted an announcement": "In einer Veranstaltung, an der ich teilnehme, wurde eine Ankündigung geteilt",
"An event I'm organizing has a new comment": "Eine Veranstaltung, an der ich teilnehme, hat einen neuen Kommentar",
"An event I'm organizing has a new participation": "Eine Veranstaltung, an der ich teilnehme, hat eine*n neue*n Teilnehmer*in",
"An event I'm organizing has a new pending participation": "Eine Veranstaltung, an der ich teilnehme, hat zu bestätigende Teilnehmer*innen",
"An event from one of my groups has been published": "Eine Veranstaltung einer meiner Gruppen wurde veröffentlicht",
"An event from one of my groups has been updated or deleted": "Eine Veranstaltung einer meiner Gruppen wurde aktualisiert oder gelöscht",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Als Instanz bezeichnen wir eine Installation der Mobilizon-Software auf einem Server. Eine Instanz kann von jedem mit Hilfe der {mobilizon_software} oder anderer kompatibler Software betrieben werden. Der Name dieser Instanz lautet „{instance_name}“. Diese Instanz ist Teil des „Fediverse“, einem Netzwerk aus vielen verbundenen Instanzen, die alle miteinander kommunizieren können. Nutzer von verschiedenen Instanzen können so - genau wie beim E-Mail-System - miteinander kommunizieren, auch wenn sie Accounts bei völlig verschiedenen Instanzen registriert haben.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Eine Programmierschnittstelle, auch API genannt (von englisch „application programming interface“) definiert ein Kommunikationsprotokoll, das Softwarekomponenten erlaubt, miteinander zu interagieren. Die Mobilizon-API ermöglicht Drittanbietersoftware beispielsweise automatisiert bestimmte Aktionen auszuführen, z.B. das Erstellen von Veranstaltungen in Ihrem Namen.",
"And {number} comments": "Und {number} Kommentare",
@ -103,6 +116,7 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Bevor Sie sich anmelden können, müssen Sie auf den darin enthaltenen Link klicken, um Ihr Konto zu validieren.",
"Begins on": "Beginnt um",
"Bold": "Fett",
"Browser notifications": "Browserbenachrichtigungen",
"By @{group}": "Von @{group}",
"By @{username}": "von @{username}",
"By others": "Von Anderen",
@ -163,6 +177,7 @@
"Create a new group": "Erstelle eine neue Gruppe",
"Create a new identity": "Erstelle eine neue Identität",
"Create a new list": "neue Liste erstellen",
"Create a new profile": "Neues Profil erstellen",
"Create a pad": "Pad erstellen",
"Create a videoconference": "Videokonferenz erstellen",
"Create a visioconference": "Erstelle eine Videokonferenz",
@ -238,6 +253,7 @@
"Duplicate": "Duplikat",
"Edit": "Bearbeiten",
"Edit post": "Beitrag bearbeiten",
"Edit profile {profile}": "Profil {profile} bearbeiten",
"Edited {ago}": "Editiert {ago}",
"Eg: Stockholm, Dance, Chess…": "z.B.: Berlin, Tanzen, Schach…",
"Either on the {instance} instance or on another instance.": "Entweder auf der Instanz {instance} oder auf einer andere Instanz.",
@ -316,6 +332,7 @@
"Gather ⋅ Organize ⋅ Mobilize": "Treffen ⋅ Organisieren ⋅ Mobilisieren",
"General": "Allgemein",
"General information": "Allgemeine Informationen",
"General settings": "Allgemeine EInstellungen",
"Getting location": "Standort ermitteln",
"Getting there": "Hin kommen",
"Glossary": "Glossar",
@ -326,6 +343,7 @@
"Group Followers": "Follower*innen dieser Gruppe",
"Group List": "Gruppenliste",
"Group Members": "Gruppenmitglieder",
"Group activity": "Gruppenereignisse",
"Group address": "Gruppenadresse",
"Group display name": "Angezeigter Gruppenname",
"Group full name": "Vollständiger Gruppenname",
@ -353,8 +371,11 @@
"I participate": "Ich nehme teil",
"I want to allow people to participate without an account.": "Ich möchte Usern erlauben ohne Konto teilzunehmen.",
"I want to approve every participation request": "Ich möchte jede Teilnahmeanfrage manuell bestätigen",
"I've been mentionned in a comment under an event": "Ich wurde in einem Kommentar unter einer Veranstaltung erwähnt",
"I've been mentionned in a group discussion": "Ich wurde in einer Gruppendiskussion erwähnt",
"ICS feed for events": "ICS-Feed mit Veranstaltungen",
"ICS/WebCal Feed": "ICS/Webcal-Feed",
"Identities": "Identitäten",
"Identity {displayName} created": "Identität {displayName} erstellt",
"Identity {displayName} deleted": "Identität {displayName} gelöscht",
"Identity {displayName} updated": "Identität {displayName} aktualisiert",
@ -443,6 +464,7 @@
"Mark as resolved": "Als gelöst markieren",
"Member": "Mitglied",
"Members": "Mitglieder",
"Mentions": "Erwähnungen",
"Message": "Nachricht",
"Mobilizon": "Mobilizon",
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon ist ein federiertes Netzwerk.Sie können mit dieser Veranstaltung von verschiedenen Servern aus interagieren.",
@ -501,14 +523,20 @@
"No instance to remove|Remove instance|Remove {number} instances": "Keine Instanz zu entfernen|Entferne Instanz|Entferne {number} Instanzen",
"No languages found": "Keine Sprachen gefunden",
"No member matches the filters": "Kein Mitglied entspricht den Filterkriterien",
"No members found": "Keine Mitglieder gefunden",
"No memberships found": "Keine Mitgliedschaften gefunden",
"No message": "Keine Nachricht",
"No moderation logs yet": "Bisher keine Moderationsprotokolle",
"No more activity to display.": "Es gibt keine weiteren anzuzeigenden Ereignisse.",
"No one is participating|One person participating|{going} people participating": "Niemand nimmt teil|Eine Person nimmt teilt|{going} nehmen teil",
"No ongoing todos": "Keine aktiven To-dos",
"No open reports yet": "Bisher keine ausstehenden Berichte",
"No organized events found": "Keine erstellte Veranstaltung gefunden",
"No organized events listed": "Keine erstellten Veranstaltungen gelistet",
"No participant matches the filters": "Kein Teilnehmer entspricht den Filterkriterien",
"No participant to approve|Approve participant|Approve {number} participants": "Keine Teilnahme zu bestätigen|Bestätige Teilnahme|Bestätige {number} Teilnahmen",
"No participant to reject|Reject participant|Reject {number} participants": "Keine Teilnahmen abzulehnen|Teilnahme ablehnen|Lehne {number} Teilnahmen ab",
"No participations listed": "Keine Teilnehmer*innen gelistet",
"No posts found": "Keine Beiträge gefunden",
"No posts yet": "Noch keine Beträge vorhanden",
"No profile matches the filters": "Kein Profil entspricht den Filterkriterien",
@ -529,8 +557,10 @@
"Nothing to see here": "Hier gibt es nichts zu sehen",
"Notification before the event": "Benachrichtigung vor der Veranstaltung",
"Notification on the day of the event": "Benachrichtigung am Tag der Veranstaltung",
"Notification settings": "Benachrichtigungseinstellungen",
"Notifications": "Benachrichtigungen",
"Notifications for manually approved participations to an event": "Benachrichtigungen bei manuell bestätigten Teilnahmen an einer Veranstlatung",
"Notify participants": "Benachrichtige Teilnehmer*innen",
"Now, create your first profile:": "Erstellen Sie jetzt Ihr erstes Profil:",
"Number of places": "Anzahl der Plätze",
"OK": "OK",
@ -558,6 +588,7 @@
"Organize and take action, freely": "Organisiert euch und legt los, selbstbestimmt",
"Organized": "Organisiert",
"Organized by": "Organisiert von",
"Organized by you": "Erstellt durch Sie",
"Organized by {name}": "Organisiert von {name}",
"Organizer": "Organisator",
"Organizer notifications": "Benachrichtigungen für Organisatoren",
@ -618,6 +649,7 @@
"Privacy policy": "Datenschutzerklärung",
"Private event": "Private Veranstaltung",
"Private feeds": "Private Feeds",
"Profile": "Profil",
"Profile feeds": "Profil-Feeds",
"Profiles": "Profile",
"Profiles and federation": "Profile und Föderation",
@ -633,10 +665,12 @@
"Publish": "Veröffentlichen",
"Published events": "Veröffentlichte Veranstaltungen",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Veröffentlichte Veranstaltungen mit <b>{comments}</b> Kommentaren und <b>{participations}</b> bestätigten Teilnahmen",
"Push": "Push",
"RSS/Atom Feed": "RSS/Atom-Feed",
"Radius": "Radius",
"Read Framasofts statement of intent on the Framablog": "Ließ Framasofts Absichtserklärung im Framablog",
"Recap every week": "Jede Woche benachrichtigen",
"Receive one email for each activity": "Eine Email für jedes Ereignis erhalten",
"Receive one email per request": "Eine E-Mail für jede Anfrage erhalten",
"Redirecting to content…": "Weiterleitung zum Inhalt…",
"Redirecting to event…": "Weiterleiten zur Veranstaltung…",
@ -700,6 +734,7 @@
"Select a radius": "Wähle einen Radius",
"Select a timezone": "Zeitzone wählen",
"Select languages": "Sprache wählen",
"Select the activities for which you wish to receive an email or a push notification.": "Wählen Sie die Ereignisse aus, für die Sie Email- oder Push-Benachrichtigungen erhalten möchten.",
"Send email": "E-Mail senden",
"Send me an email to reset my password": "Sende mir eine E-Mail, um mein Passwort zurückzusetzen",
"Send me the confirmation email once again": "Sende mir noch eine Bestätigungsmail",
@ -859,6 +894,7 @@
"Uploaded media size": "Größe der hochgeladenen Medien",
"Use my location": "Nutzte meinen Standort",
"User": "Nutzer",
"User settings": "Nutzer*inneneinstellungen",
"Username": "Nutzername",
"Users": "Nutzer",
"View a reply": "|Zeige eine Antwort|Zeige {totalReplies} Antworten",
@ -892,6 +928,7 @@
"We'll use your timezone settings to send a recap of the morning of the event.": "Wir nutzen Ihre Zeitzonen-Einstellung, um Ihnen am Morgen der Veranstaltung eine Erinnerung zu senden.",
"Website": "Webseite",
"Website / URL": "Website / URL",
"Weekly email summary": "Wöchentliche E-Mail-Zusammenfassungen",
"Welcome back {username}!": "Willkommen zurück {username}!",
"Welcome back!": "Willkommen zurück!",
"Welcome on your administration panel": "Willkommen in deiner Administrationsansicht",
@ -916,6 +953,7 @@
"You are not an administrator for this group.": "Sie sind kein Administrator dieser Gruppe.",
"You are not part of any group": "Sie sind nicht Teil einer Gruppe",
"You are not part of any group.": "Sie sind kein Teil einer Gruppe.",
"You are offline": "Sie sind offline",
"You are participating in this event anonymously": "Sie nehmen anonym an dieser Veranstaltung teil",
"You are participating in this event anonymously but didn't confirm participation": "Sie nehmen an dieser Veranstaltung anonym teil, haben aber Ihre Teilnahme noch nicht bestätigt",
"You can add tags by hitting the Enter key or by adding a comma": "Sie können Schlagworte hinzufügen, indem Sie Enter drücken oder durch ein Komma trennen",
@ -924,6 +962,7 @@
"You can't change your password because you are registered through {provider}.": "Sie können Ihr Passwort nicht ändern, weil Sie über {provider} angemeldet sind.",
"You can't remove your last identity.": "Du kannst deine letzte Identität nicht löschen.",
"You can't reset your password because you use a 3rd-party auth provider to login.": "Du kannst dein Passwort nicht zurücksetzen, weil du über einen Drittanbieter eingeloggt bist.",
"You changed your email or password": "Sie haben Ihre Email-Adresse oder Ihr Passwort geändert",
"You created the discussion {discussion}.": "Sie haben die Diskussion {discussion} erstellt.",
"You created the event {event}.": "Sie haben die Veranstaltung {event} erstellt.",
"You created the folder {resource}.": "Sie haben den Ordner {resource} erstellt.",
@ -1072,6 +1111,8 @@
"{moderator} added a note on {report}": "{moderator} hat eine Notiz zu {report} hinzugefügt",
"{moderator} closed {report}": "{moderator} hat die Meldung {report} geschlossen",
"{moderator} deleted an event named \"{title}\"": "{moderator} hat eine Veranstaltung namens „{title}“ gelöscht",
"{moderator} has deleted a comment from {author}": "{moderator} hat einen Kommentar von {author} gelöscht",
"{moderator} has deleted a comment from {author} under the event {event}": "{moderator} hat einen Kommentar von {author} unter der Veranstaltung {event} gelöscht",
"{moderator} has deleted user {user}": "{moderator} hat den Nutzer {user} gelöscht",
"{moderator} has unsuspended profile {profile}": "{moderator} hat das Profil {profil} gesperrt",
"{moderator} marked {report} as resolved": "{moderator} hat {report} als erledigt markiert",
@ -1079,6 +1120,7 @@
"{moderator} suspended profile {profile}": "{moderator} hat das Profil {profile} gesperrt",
"{nb} km": "{nb} km",
"{number} members": "{number} Mitglieder",
"{number} memberships": "{number} Mitgliedschaften",
"{number} organized events": "Keine organisierten Veranstaltungen|Eine organisierte Veranstaltung|{number} organisierte Veranstaltungen",
"{number} participations": "Keine Teilnehmer|Ein Teilnehmer|{number} Teilnehmer",
"{number} posts": "Keine Beiträge |Ein Beitrag|{number} Beiträge",

View File

@ -1063,5 +1063,84 @@
"Ask your instance admin to {enable_feature}.": "Ask your instance admin to {enable_feature}.",
"Event URL": "Event URL",
"Copy URL to clipboard": "Copy URL to clipboard",
"Group URL": "Group URL"
"Group URL": "Group URL",
"View less": "View less",
"View more": "View more",
"Breadcrumbs": "Breadcrumbs",
"Other actions": "Other actions",
"Only group moderators can create, edit and delete events.": "Only group moderators can create, edit and delete events.",
"+ Create a post": "+ Create a post",
"Edited {relative_time} ago": "Edited {relative_time} ago",
"Members-only post": "Members-only post",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.",
"Find or add an element": "Find or add an element",
"e.g. Accessibility, Twitch, PeerTube": "e.g. Accessibility, Twitch, PeerTube",
"Add new…": "Add new…",
"No results for {search}": "No results for {search}",
"Wheelchair accessibility": "Wheelchair accessibility",
"Whether the event is accessible with a wheelchair": "Whether the event is accessible with a wheelchair",
"Not accessible with a wheelchair": "Not accessible with a wheelchair",
"Partially accessible with a wheelchair": "Partially accessible with a wheelchair",
"Fully accessible with a wheelchair": "Fully accessible with a wheelchair",
"YouTube replay": "YouTube replay",
"The URL where the event live can be watched again after it has ended": "The URL where the event live can be watched again after it has ended",
"Twitch replay": "Twitch replay",
"PeerTube replay": "PeerTube replay",
"PeerTube live": "PeerTube live",
"The URL where the event can be watched live": "The URL where the event can be watched live",
"Twitch live": "Twitch live",
"YouTube live": "YouTube live",
"Event metadata": "Event metadata",
"Framadate poll": "Framadate poll",
"The URL of a poll where the choice for the event date is happening": "The URL of a poll where the choice for the event date is happening",
"View account on {hostname} (in a new window)": "View account on {hostname} (in a new window)",
"Twitter account": "Twitter account",
"A twitter account handle to follow for event updates": "A twitter account handle to follow for event updates",
"Fediverse account": "Fediverse account",
"A fediverse account URL to follow for event updates": "A fediverse account URL to follow for event updates",
"Element title": "Element title",
"Element value": "Element value",
"Subtitles": "Subtitles",
"Whether the event live video is subtitled": "Whether the event live video is subtitled",
"The event live video contains subtitles": "The event live video contains subtitles",
"The event live video does not contain subtitles": "The event live video does not contain subtitles",
"Sign Language": "Sign Language",
"Whether the event is interpreted in sign language": "Whether the event is interpreted in sign language",
"The event has a sign language interpreter": "The event has a sign language interpreter",
"The event hasn't got a sign language interpreter": "The event hasn't got a sign language interpreter",
"Online ticketing": "Online ticketing",
"An URL to an external ticketing platform": "An URL to an external ticketing platform",
"Price sheet": "Price sheet",
"A link to a page presenting the price options": "A link to a page presenting the price options",
"Integrate this event with 3rd-party tools and show metadata for the event.": "Integrate this event with 3rd-party tools and show metadata for the event.",
"This URL doesn't seem to be valid": "This URL doesn't seem to be valid",
"Schedule": "Schedule",
"A link to a page presenting the event schedule": "A link to a page presenting the event schedule",
"Accessibility": "Accessibility",
"Live": "Live",
"Replay": "Replay",
"Tools": "Tools",
"Social": "Social",
"Details": "Details",
"Booking": "Booking",
"Filter by profile or group name": "Filter by profile or group name",
"Filter by name": "Filter by name",
"Redirecting in progress…": "Redirecting in progress…",
"Zoom in": "Zoom in",
"Zoom out": "Zoom out",
"Show me where I am": "Show me where I am",
"Video Conference": "Video Conference",
"Jisti Meet": "Jisti Meet",
"The Jitsi Meet video teleconference URL": "The Jitsi Meet video teleconference URL",
"Zoom": "Zoom",
"The Zoom video teleconference URL": "The Zoom video teleconference URL",
"Microsoft Teams": "Microsoft Teams",
"The Microsoft Teams video teleconference URL": "The Microsoft Teams video teleconference URL",
"Google Meet": "Google Meet",
"The Google Meet video teleconference URL": "The Google Meet video teleconference URL",
"Big Blue Button": "Big Blue Button",
"The Big Blue Button video teleconference URL": "The Big Blue Button video teleconference URL",
"Etherpad notes": "Etherpad notes",
"The URL of a pad where notes are being taken collaboratively": "The URL of a pad where notes are being taken collaboratively",
"https://mensuel.framapad.org/p/some-secret-token": "https://mensuel.framapad.org/p/some-secret-token"
}

View File

@ -5,6 +5,7 @@
"(this link)": "(este enlace)",
"+ Add a resource": "+ Agregar un recurso",
"+ Add a todo": "+ Add to all",
"+ Create a post": "+ Crear una publicación",
"+ Create an event": "+ Crear un evento",
"+ Post a public message": "+ Publica un mensaje público",
"+ Start a discussion": "+ Iniciar una discusión",
@ -19,7 +20,10 @@
"A cookie is a small file containing informations that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows to store more data.": "Una cookie es un pequeño archivo que contiene información que se envía a su computadora cuando visita un sitio web. Cuando vuelve a visitar el sitio, la cookie permite que ese sitio reconozca su navegador. Las cookies pueden almacenar las preferencias del usuario y otra información. Puede configurar su navegador para rechazar todas las cookies. Sin embargo, esto puede provocar que algunas funciones o servicios del sitio web funcionen parcialmente. El almacenamiento local funciona de la misma manera pero permite almacenar más datos.",
"A discussion has been created or updated": "Se ha creado o actualizado una discusión",
"A federated software": "Un software federado",
"A fediverse account URL to follow for event updates": "Una URL de cuenta de fediverse a seguir para actualizaciones de eventos",
"A group with this name already exists": "Ya existe un grupo con este nombre",
"A link to a page presenting the event schedule": "Un enlace a una página que presenta el calendario del evento",
"A link to a page presenting the price options": "Un enlace a una página que presenta las opciones de precio",
"A member has been updated": "Un miembro ha sido actualizado",
"A member requested to join one of my groups": "Un miembro solicitó unirse a uno de mis grupos",
"A new version is available.": "Una nueva version esta disponible.",
@ -32,6 +36,7 @@
"A practical tool": "Una herramienta práctica",
"A resource has been created or updated": "Traducción",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Un breve eslogan para la página de inicio de su instancia. El valor predeterminado es \"Reunir ⋅ Organizar ⋅ Movilizar\"",
"A twitter account handle to follow for event updates": "Un identificador de cuenta de Twitter a seguir para actualizaciones de eventos",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Una herramienta fácil de usar, emancipadora y ética, para reunir, organizar y movilizar.",
"A validation email was sent to {email}": "Un correo electrónico de confirmación fue enviado a {email}",
"API": "API",
@ -45,6 +50,7 @@
"About {instance}": "Acerca de {instance}",
"Accept": "Aceptar",
"Accepted": "Aceptado",
"Accessibility": "Accesibilidad",
"Accessible only to members": "Accesible solo para miembros",
"Accessible through link": "Accesible a través del enlace",
"Account": "Cuenta",
@ -65,6 +71,7 @@
"Add a todo": "Agrega una tarea pendiente",
"Add an address": "Añade una dirección",
"Add an instance": "Añade una instancia",
"Add new…": "Agregar nuevo…",
"Add some tags": "Añade algunas etiquetas",
"Add to my calendar": "Añadir a mi calendario",
"Additional comments": "Comentarios adicionales",
@ -80,6 +87,7 @@
"Allow all comments": "Permitir todos los comentarios",
"Allow all comments from users with accounts": "Permitir todos los comentarios de los usuarios registrados",
"Allow registrations": "Permitir registros",
"An URL to an external ticketing platform": "Una URL a una plataforma de venta de entradas externa",
"An error has occured while refreshing the page.": "Ha ocurrido un error al actualizar la página.",
"An error has occured. Sorry about that. You may try to reload the page.": "Ha ocurrido un error. Lo siento por eso. Puede intentar volver a cargar la página.",
"An error has occurred.": "Se ha producido un error.",
@ -125,6 +133,8 @@
"Begins on": "Comienza en",
"Bio": "Bio",
"Bold": "Negrita",
"Booking": "Reservacion",
"Breadcrumbs": "Migajas",
"Browser notifications": "Notificaciones del navegador",
"By @{group}": "Por @{group}",
"By @{username}": "Por @{username}",
@ -250,6 +260,7 @@
"Deleting your Mobilizon account": "Eliminando tu cuenta de Mobilizon",
"Demote": "Degradar",
"Description": "Descripción",
"Details": "Detalles",
"Didn't receive the instructions ?": "¿No recibiste las instrucciones?",
"Didn't receive the instructions?": "¿No recibiste las instrucciones?",
"Disabled": "Deshabilitado",
@ -272,12 +283,15 @@
"Edit post": "Editar publicación",
"Edit profile {profile}": "Editar perfil {profile}",
"Edited {ago}": "Editado {ago}",
"Edited {relative_time} ago": "Editado hace {relative_time}",
"Eg: Stockholm, Dance, Chess…": "Ej .: Estocolmo, Danza, Ajedrez …",
"Either on the {instance} instance or on another instance.": "Ya sea en la instancia {instancia} o en otra instancia.",
"Either the account is already validated, either the validation token is incorrect.": "O la cuenta ya está validada, o bien el testigo de validación es incorrecto.",
"Either the email has already been changed, either the validation token is incorrect.": "O el correo electrónico ya se ha cambiado, o bien el token de validación es incorrecto.",
"Either the participation has already been validated, either the validation token is incorrect.": "O la participación ya ha sido validada, o bien el token de validación es incorrecto.",
"Either the participation request has already been validated, either the validation token is incorrect.": "O la solicitud de participación ya se ha validado o el token de validación es incorrecto.",
"Element title": "Título del elemento",
"Element value": "Valor del elemento",
"Email": "Correo elecxtrónico",
"Email address": "Dirección de correo electrónico",
"Email notifications": "Notificaciónes de Correo Electrónico",
@ -314,6 +328,7 @@
"Event creation": "Creación de evento",
"Event edition": "Edición del evento",
"Event list": "Lista de eventos",
"Event metadata": "Metadatos de eventos",
"Event not found.": "Evento no encontrado.",
"Event page settings": "Configuración de la página del evento",
"Event to be confirmed": "Evento por confirmar",
@ -333,10 +348,14 @@
"Features": "Caracteristicas",
"Federated Group Name": "Nombre del grupo federado",
"Federation": "Federación",
"Fediverse account": "Cuenta Fediverse",
"Fetch more": "Buscar más",
"Filter by name": "Filtrar por nombre",
"Filter by profile or group name": "Filtrar por perfil o nombre de grupo",
"Find an address": "Buscar una dirección",
"Find an instance": "Buscar una instancia",
"Find another instance": "Encuentra otra instancia",
"Find or add an element": "Encuentra o agrega un elemento",
"Followed by {count} persons": "Seguido por {count} personas",
"Follower": "Seguidor",
"Followers": "Seguidores",
@ -346,12 +365,14 @@
"For instance: London, Taekwondo, Architecture…": "Por ejemplo: Londres, Taekwondo, Arquitectura …",
"Forgot your password ?": "¿Olvidaste tu contraseña ?",
"Forgot your password?": "¿Olvidaste tu contraseña?",
"Framadate poll": "Encuesta Framadate",
"From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves up</b> inside MeetUp?": "Desde una fiesta de cumpleaños con amigos y familiares hasta una marcha por el cambio climático, en este momento, nuestras reuniones están <b> atrapadas dentro de las plataformas de los gigantes tecnológicos </b>. ¿Cómo podemos organizarnos, cómo podemos hacer clic en \"Asistir\", sin <b> proporcionar datos privados </b> a Facebook o <b> encerrarnos </b> dentro de MeetUp?",
"From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?": "Desde una fiesta de cumpleaños con amigos y familiares hasta una marcha por el cambio climático, ahora mismo, nuestras reuniones están <b> atrapadas dentro de las plataformas de los gigantes tecnológicos </b>. ¿Cómo podemos organizarnos, cómo podemos hacer clic en \"Asistir\" sin <b> proporcionar datos privados </b> a Facebook o <b> encerrarnos </b> dentro de MeetUp?",
"From the {startDate} at {startTime} to the {endDate}": "Desde {startDate} en {startTime} hasta {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Desde el {startDate} en {startTime} hasta el {endDate} en {endTime}",
"From the {startDate} to the {endDate}": "Desde el {startDate} hasta el {endDate}",
"From yourself": "De ti mismo",
"Fully accessible with a wheelchair": "Totalmente accesible con silla de ruedas",
"Gather ⋅ Organize ⋅ Mobilize": "Reúna ⋅ Organice ⋅ Movilice",
"General": "General",
"General information": "Información general",
@ -438,6 +459,7 @@
"Instances": "Instancias",
"Instances following you": "Instancias siguiéndote",
"Instances you follow": "Instancias que sigues",
"Integrate this event with 3rd-party tools and show metadata for the event.": "Integre este evento con herramientas de terceros y muestre metadatos para el evento.",
"Interact with a remote content": "Interactuar con un contenido remoto",
"Invite a new member": "Invita a un nuevo miembro",
"Invite member": "Miembro invitado",
@ -473,6 +495,7 @@
"Like title update, start or end date change, event being confirmed or cancelled.": "Como cambiar el título del evento, su fecha de inicio o finalización, o ser confirmado o cancelado.",
"Limited number of places": "Número limitado de plazas",
"List title": "Título de la lista",
"Live": "En directo",
"Load more": "Carga más",
"Load more activities": "Cargar más actividades",
"Loading comments…": "Cargando comentarios…",
@ -494,6 +517,7 @@
"Mark as resolved": "Marca como resuelto",
"Member": "Miembro",
"Members": "Miembros",
"Members-only post": "Publicación solo para miembros",
"Mentions": "Menciones",
"Message": "Mensaje",
"Mobilizon": "Mobilizon",
@ -583,9 +607,11 @@
"No resources selected": "Ningún recurso seleccionado|Un recurso seleccionado|{count} recursos seleccionados",
"No resources yet": "Aún no hay recursos",
"No results for \"{queryText}\"": "No hay resultados para \"{queryText}\"",
"No results for {search}": "No hay resultados para {search}",
"No rules defined yet.": "No hay reglas definidas todavía.",
"No user account with this email was found. Maybe you made a typo?": "No se encontró ninguna cuenta de usuario con este correo electrónico. Tal vez hiciste un error tipográfico?",
"None": "Ninguno",
"Not accessible with a wheelchair": "No accesible con silla de ruedas",
"Not approved": "No aprovado",
"Not confirmed": "Sin confirmar",
"Notes": "Notas",
@ -608,6 +634,7 @@
"On {instance} and other federated instances": "En {instance} y otras instancias federadas",
"One person is going": "Nadie va|Una persona va|personas {approved} van",
"Ongoing tasks": "Tareas en curso",
"Online ticketing": "Venta de entradas en línea",
"Only accessible through link": "Solo accesible a través del enlace",
"Only accessible through link (private)": "Solo accesible a través del enlace (privado)",
"Only accessible through link and search (private)": "Solo accesible a través de enlace y búsqueda (privado)",
@ -615,6 +642,7 @@
"Only alphanumeric characters and underscores are supported.": "Solo se admiten caracteres alfanuméricos y guiones bajos.",
"Only alphanumeric lowercased characters and underscores are supported.": "Solo se admiten caracteres alfanuméricos en minúscula y guiones bajos.",
"Only group members can access discussions": "Solo los miembros del grupo pueden acceder a las discusiones",
"Only group moderators can create, edit and delete events.": "Solo los moderadores de grupo pueden crear, editar y eliminar eventos.",
"Only group moderators can create, edit and delete posts.": "Solo los moderadores de grupo pueden crear, editar y eliminar publicaciones.",
"Open": "Abrir",
"Open a topic on our forum": "Abrir un tema en nuestro foro",
@ -630,6 +658,7 @@
"Organizer notifications": "Notificaciones del organizador",
"Organizers": "Organizadores",
"Other": "Otro",
"Other actions": "Otras acciones",
"Other notification options:": "Otras opciones de notificación:",
"Other software may also support this.": "Otro software también puede soportar esto.",
"Otherwise this identity will just be removed from the group administrators.": "De lo contrario, esta identidad solo se eliminará de los administradores del grupo.",
@ -637,6 +666,7 @@
"Page limited to my group (asks for auth)": "Página limitada a mi grupo (solicita autenticación)",
"Page not found": "Página no encontrada",
"Parent folder": "Carpeta superior",
"Partially accessible with a wheelchair": "Parcialmente accesible con silla de ruedas",
"Participant": "Participante",
"Participant already was rejected.": "El participante ya fue rechazado.",
"Participant has already been approved as participant.": "El participante ya ha sido aprobado como participante.",
@ -652,6 +682,8 @@
"Password (confirmation)": "Contraseña (confirmación)",
"Password reset": "Restablecer la contraseña",
"Past events": "Eventos pasados",
"PeerTube live": "PeerTube en vivo",
"PeerTube replay": "Repetición de PeerTube",
"Pending": "Pendiente",
"Personal feeds": "Flujos personales",
"Pick": "Recoger",
@ -679,6 +711,7 @@
"Preferences": "Preferencias",
"Previous": "Anterior",
"Previous page": "Pagina anterior",
"Price sheet": "Hoja de precios",
"Privacy Policy": "Política de privacidad",
"Privacy policy": "Política de privacidad",
"Private event": "Evento privado",
@ -707,6 +740,7 @@
"Recap every week": "Recordatorio semanal",
"Receive one email for each activity": "Reciba un correo electrónico por cada actividad",
"Receive one email per request": "Recibir un correo electrónico por solicitud",
"Redirecting in progress…": "Redirección en progreso…",
"Redirecting to content…": "Redirigiendo al contenido…",
"Redirecting to event…": "Redirigiendo al evento …",
"Refresh profile": "Actualizar perfil",
@ -731,6 +765,7 @@
"Rename": "Renombrar",
"Rename resource": "Renombrar recurso",
"Reopen": "Reabrir",
"Replay": "Repetición",
"Reply": "Respuesta",
"Report": "Declarar",
"Report #{reportNumber}": "Informe #{reportNumber}",
@ -760,6 +795,7 @@
"SSL/TLS": "SSL/TLS",
"Save": "Guardar",
"Save draft": "Guardar borrador",
"Schedule": "Calendario",
"Search": "Buscar",
"Search events, groups, etc.": "Buscar eventos, grupos, etc.",
"Search results: \"{search}\"": "Resultados de búsqueda: \"{search}\"",
@ -784,13 +820,16 @@
"Share this group": "Comparte este grupo",
"Short bio": "Breve biografía",
"Show map": "Mostrar mapa",
"Show me where I am": "Muéstrame donde estoy",
"Show remaining number of places": "Muestra el número restante de plazas",
"Show the time when the event begins": "Muestra la hora en que comienza el evento",
"Show the time when the event ends": "Muestra la hora en que finaliza el evento",
"Sign Language": "Lenguaje de señas",
"Sign in with": "Inicia sesión con",
"Sign up": "Regístrate",
"Since you are a new member, private content can take a few minutes to appear.": "Dado que es un miembro nuevo, el contenido privado puede tardar unos minutos en aparecer.",
"So that, right from its conception, Mobilizon would {fit_needs_uses_people} who are going to use it.": "De modo que, desde su concepción, Mobilizon podría {fit_needs_uses_people} quién lo va a usar.",
"Social": "Social",
"Software to the people": "Software para la gente",
"Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Algunos términos, técnicos o de otro tipo, utilizados en el texto a continuación pueden abarcar conceptos que son difíciles de comprender. Hemos proporcionado un glosario aquí para ayudarlo a comprenderlos mejor:",
"Starts on…": "Comienza en …",
@ -798,6 +837,7 @@
"Statut": "Estado",
"Street": "Calle",
"Submit": "Enviar",
"Subtitles": "Subtítulos",
"Suspend": "Suspender",
"Suspend group": "Suspender grupo",
"Suspended": "Suspendido",
@ -808,16 +848,23 @@
"Terms": "Condiciones",
"Terms of service": "Términos de servicio",
"Text": "Texto",
"The URL of a poll where the choice for the event date is happening": "La URL de una encuesta en la que se realiza la elección de la fecha del evento",
"The URL where the event can be watched live": "La URL donde se puede ver el evento en vivo",
"The URL where the event live can be watched again after it has ended": "La URL donde se puede volver a ver el evento en vivo una vez finalizado",
"The account's email address was changed. Check your emails to verify it.": "Se cambió la dirección de correo electrónico de la cuenta. Revise sus correos electrónicos para verificarlo.",
"The actual number of participants may differ, as this event is hosted on another instance.": "El número real de participantes puede diferir ya que este evento se aloja en otra instancia.",
"The content came from another server. Transfer an anonymous copy of the report?": "El contenido vino de otro servidor. ¿Transferir una copia anónima del informe?",
"The current identity doesn't have any permission on this event. You should probably change it.": "La identidad actual no tiene ningún permiso para este evento. Probablemente deberías cambiarlo.",
"The current password is invalid": "La contraseña actual no es válida",
"The draft event has been updated": "El borrador del evento ha sido actualizado",
"The event has a sign language interpreter": "El evento cuenta con intérprete de lengua de signos",
"The event has been created as a draft": "El evento ha sido creado como borrador",
"The event has been published": "El evento ha sido publicado",
"The event has been updated": "El evento ha sido actualizado",
"The event has been updated and published": "El evento ha sido actualizado y publicado",
"The event hasn't got a sign language interpreter": "El evento no tiene intérprete de lenguaje de señas",
"The event live video contains subtitles": "El video en vivo del evento contiene subtítulos",
"The event live video does not contain subtitles": "El video en vivo del evento no contiene subtítulos",
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "El organizador del evento ha elegido validar las participaciones manualmente. ¿Desea agregar una pequeña nota para explicar por qué desea participar en este evento?",
"The event organizer didn't add any description.": "El organizador del evento no agregó ninguna descripción.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "El organizador del evento aprueba manualmente las participaciones. Dado que ha elegido participar sin una cuenta, explique por qué desea participar en este evento.",
@ -865,6 +912,7 @@
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Estos flujos contienen datos de eventos para los eventos en los que alguno de sus perfiles es participante o creador. Deberías mantenerlos privados. Puede encontrar flujos para perfiles específicos en cada página de edición de perfil.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Estos flujos contienen datos de eventos para los eventos para los que este perfil específico es un participante o creador. Deberías mantenerlos privados. Puede encontrar flujos para todos sus perfiles en la configuración de notificaciones.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Esta instancia de Mobilizon y este organizador de eventos permiten participaciones anónimas pero requieren validación mediante confirmación por correo electrónico.",
"This URL doesn't seem to be valid": "Esta URL no parece ser válida",
"This URL is not supported": "Esta URL no es compatible",
"This email is already registered as participant for this event": "Este correo electrónico ya está registrado como participante para este evento",
"This email is already used.": "Este correo electrónico ya está en uso.",
@ -886,6 +934,7 @@
"This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Esto es como su nombre de usuario federado (<code>{username}</code>) para grupos. Permitirá que el grupo se encuentre en la federación y se garantiza que será único.",
"This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.": "Esto es como su nombre de usuario federado (<code>{username}</code>) para grupos. Le permitirá ser encontrado en la federación y se garantiza que será único.",
"This month": "Este mes",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Esta publicación es accesible solo para miembros. Tiene acceso a él con fines de moderación solo porque es un moderador de instancia.",
"This setting will be used to display the website and send you emails in the correct language.": "Esta configuración se utilizará para mostrar el sitio web y enviarle correos electrónicos en el idioma correcto.",
"This user has been disabled": "Este usuario ha sido deshabilitado",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Este sitio web no está moderado y los datos que ingrese se destruirán automáticamente todos los días a las 00:01 (zona horaria de París).",
@ -906,8 +955,12 @@
"To register for an event by choosing one of your identities": "Para registrarse en un evento eligiendo una de sus identidades",
"Today": "Hoy",
"Tomorrow": "Mañana",
"Tools": "Herramientas",
"Transfer to {outsideDomain}": "Transferir a {outsideDomain}",
"Triggered profile refreshment": "Refresco de perfil activado",
"Twitch live": "Twitch en vivo",
"Twitch replay": "Repetición de Twitch",
"Twitter account": "Cuenta de Twitter",
"Type": "Tipo",
"Type or select a date…": "Escriba o seleccione una fecha …",
"URL": "URL",
@ -947,6 +1000,7 @@
"Username": "Nombre de usuario",
"Users": "Los usuarios",
"View a reply": "|Ver una respuesta|Ver {totalReplies} respuestas",
"View account on {hostname} (in a new window)": "Ver cuenta en {hostname} (en una nueva ventana)",
"View all": "Ver todo",
"View all discussions": "Ver todas las discusiones",
"View all events": "Ver todos los eventos",
@ -956,6 +1010,8 @@
"View all upcoming events": "Ver todos los próximos eventos",
"View event page": "Ver página del evento",
"View everything": "Ver todo",
"View less": "Ver menos",
"View more": "Ver más",
"View page on {hostname} (in a new window)": "Ver página en {hostname} (en una nueva ventana)",
"Visibility was set to an unknown value.": "La visibilidad se estableció en un valor desconocido.",
"Visibility was set to private.": "La visibilidad se estableció en privada.",
@ -988,8 +1044,12 @@
"Welcome on your administration panel": "Bienvenido en tu panel de administración",
"Welcome to Mobilizon, {username}!": "¡Bienvenido a Mobilizon, {username}!",
"What can I do to help?": "¿Que puedo hacer para ayudar?",
"Wheelchair accessibility": "Accesibilidad para sillas de ruedas",
"When a moderator from the group creates an event and attributes it to the group, it will show up here.": "Cuando un moderador del grupo crea un evento y lo atribuye al grupo, se mostrará aquí.",
"When someone from the group creates an event and attributes it to the group, it will show up here.": "Cuando alguien del grupo crea un evento y lo atribuye al grupo, se mostrará aquí.",
"Whether the event is accessible with a wheelchair": "Si el evento es accesible con silla de ruedas",
"Whether the event is interpreted in sign language": "Si el evento se interpreta en lenguaje de señas",
"Whether the event live video is subtitled": "Si el video en vivo del evento está subtitulado",
"Who can view this event and participate": "Quién puede ver este evento y participar",
"Who can view this post": "Quién puede ver esta publicación",
"Who published {number} events": "Quién publicó {número} eventos",
@ -1087,6 +1147,8 @@
"You'll need to transmit the group URL so people may access the group's profile.": "Deberá transmitir la URL del grupo para que las personas puedan acceder al perfil del grupo.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Deberá transmitir la URL del grupo para que las personas puedan acceder al perfil del grupo. El grupo no se podrá encontrar en la búsqueda de Mobilizon ni en los motores de búsqueda habituales.",
"You'll receive a confirmation email.": "Recibirá un correo electrónico de confirmación.",
"YouTube live": "YouTube en vivo",
"YouTube replay": "Reproducción de YouTube",
"Your account has been successfully deleted": "Su cuenta ha sido eliminada exitosamente",
"Your account has been validated": "Su cuenta ha sido validada",
"Your account is being validated": "Su cuenta esta siendo validada",
@ -1119,6 +1181,8 @@
"Your timezone was detected as {timezone}.": "Su zona horaria se detectó como {timezone}.",
"Your timezone {timezone} isn't supported.": "Tu zona horaria {timezone} no es compatible.",
"Your upcoming events": "Tus próximos eventos",
"Zoom in": "Zoom adelante",
"Zoom out": "Zoom atras",
"[This comment has been deleted by it's author]": "[Este comentario ha sido borrado por su autor]",
"[This comment has been deleted]": "[Este comentario ha sido eliminado]",
"[deleted]": "[eliminado]",
@ -1135,6 +1199,7 @@
"default Mobilizon terms": "términos predeterminados de Mobilizon",
"digital habits of activists": "hábitos digitales de activistas",
"e.g. 10 Rue Jangot": "e.j. 10 Rue Jangot",
"e.g. Accessibility, Twitch, PeerTube": "p.ej. Accesibilidad, Twitch, PeerTube",
"enable the feature": "habilitar la función",
"explore the events": "explorar los eventos",
"explore the groups": "explorar los grupos",

View File

@ -12,15 +12,23 @@
"@{group}": "@{group}",
"@{username}": "@{username}",
"@{username} ({role})": "@{username} ({role)}",
"@{username}'s follow request was accepted": "Käyttäjän @{username} seurauspyyntö hyväksyttiin",
"@{username}'s follow request was rejected": "Käyttäjän @{username} seuraamispyyntö hylättiin",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Evästeellä tarkoitetaan sellaista tietoa, jonka palvelun tarjoajan palvelin lähettää käyttäjän selainohjelmalle pyytäen selainta tallentamaan tiedon käyttäjän päätelaitteelle ja jota kyseinen palvelun tarjoajan palvelin voi myöhemmin pyytää takaisin. Kyse on käytännössä pienestä tietomäärästä, tyypillisesti lyhyestä tekstistä. Voit asettaa selaimen kieltäytymään kaikista evästeistä, mutta tämä saattaa rikkoa joitakin toiminnallisuuksia.",
"A cookie is a small file containing informations that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows to store more data.": "Evästeet ovat pieniä tiedostoja, joita verkkosivut lähettävät käyttäjän tietokoneelle käyttäjän vieraillessa verkkosivuilla. Kun käyttäjä vierailee sivulla uudelleen, sivusto tunnistaa käyttäjän selaimen evästeen avulla. Evästeisiin voidaan tallentaa käyttäjän valintoja ja muuta tietoa. Selaimen voi asettaa hylkäämään kaikki evästeet. Tällöin kuitenkin osa verkkosivun toiminnoista tai palveluista voi lakata toimimasta. Paikallisesti tallennettavat tiedot toimivat samalla tavoin, mutta niihin voidaan tallentaa suurempia tietomääriä.",
"A discussion has been created or updated": "Keskustelu on luotu tai päivitetty",
"A federated software": "Federoitu ohjelmisto",
"A member has been updated": "Käyttäjä on päivitetty",
"A member requested to join one of my groups": "Käyttäjä pyysi liittyä yhteen ryhmistäni",
"A new version is available.": "Uusi versio on saatavilla.",
"A place for your code of conduct, rules or guidelines. You can use HTML tags.": "Käytössäännöille, säännöille ja ohjeille varattu tila. HTML-tunnisteita voi käyttää.",
"A place to explain who you are and the things that set your instance apart. You can use HTML tags.": "Tässä voi esitellä ylläpitäjätahoa ja tämän palvelimen erityispiirteitä. HTML-tunnisteita voi käyttää.",
"A place to publish something to the whole world, your community or just your group members.": "Paikka, jossa voit julkaista asioita koko maailmalle, yhteisöllesi tai pelkästään ryhmäsi jäsenille.",
"A place to store links to documents or resources of any type.": "Paikka, johon voit tallentaa linkkejä dokumentteihin tai muihin resursseihin.",
"A post has been published": "Viesti on julkaistu",
"A post has been updated": "Viesti on päivitetty",
"A practical tool": "Kätevä työkalu",
"A resource has been created or updated": "Resurssi on luotu tai päivitetty",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Lyhyt teksti instanssisi kotisivulle. Oletuksena \"Kokoonnu ⋅ Järjestä ⋅ Mobilizoi\"",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Käyttäjäystävällinen, vapauttava ja eettinen työkalu tapahtumien järjestämiseen, organisointiin ja mobilisointiin.",
"A validation email was sent to {email}": "Vahvistussähköposti lähetettiin osoitteeseen {email}",
@ -39,8 +47,10 @@
"Accessible through link": "Pääsy linkin kautta",
"Account": "Tili",
"Actions": "Toimenpiteet",
"Activate browser push notifications": "Ota käyttöön selaimen push-ilmoitukset",
"Activated": "Käytössä",
"Active": "Aktiivinen",
"Activity": "Toiminta",
"Actor": "Toimija",
"Add": "Lisää",
"Add / Remove…": "Lisää/poista…",
@ -59,23 +69,33 @@
"Admin settings successfully saved.": "Ylläpitoasetukset tallennettu.",
"Administration": "Ylläpito",
"Administrator": "Ylläpitäjä",
"All activities": "Kaikki toiminta",
"All good, let's continue!": "Kaikki kunnossa, eteenpäin!",
"All group members and other eventual server admins will still be able to view this information.": "Ryhmän kaikki jäsenet ja palvelimen mahdolliset muut ylläpitäjät voivat edelleen nähdä tämän tiedon.",
"All the places have already been taken": "Kaikki paikat on jo varattu",
"Allow all comments": "Salli kaikki kommentit",
"Allow all comments from users with accounts": "Salli kommentit kirjautuneilta käyttäjiltä",
"Allow registrations": "Salli rekisteröityminen",
"An error has occured while refreshing the page.": "Sivua päivitettäessä tapahtui virhe.",
"An error has occured. Sorry about that. You may try to reload the page.": "Tapahtui virhe. Yritä ladata sivu uudelleen.",
"An error has occurred.": "Tapahtui virhe.",
"An ethical alternative": "Eettinen vaihtoehto",
"An event I'm going to has been updated": "Tapahtuma, jonne olen menossa, on päivitetty",
"An event I'm going to has posted an announcement": "Tapahtuma, jonne olen menossa, teki julkaisun",
"An event I'm organizing has a new comment": "Organisoimassani tapahtumassa on uusi kommentti",
"An event I'm organizing has a new participation": "Organisoimassani tapahtumassa on uusi osallistuja",
"An event I'm organizing has a new pending participation": "Organisoimassani tapahtumassa on uusi osallistumispyyntö",
"An event from one of my groups has been published": "Tapahtuma ryhmästäni on julkaistu",
"An event from one of my groups has been updated or deleted": "Tapahtuma ryhmistäni on päivitetty tai poistettu",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Palvelin tarkoittaa palvelintietokoneelle asennettua Mobilizon-ohjelmaa. Palvelinta voi käyttää kuka tahansa, jolla on käytössään {mobilizon_software} tai jokin muu federoituva sovellus, eli se on osa niin kutsuttua fediversumia. Tämän palvelimen nimi on {instance_name}. Mobilizon on useiden palvelinten muodostama federoituva verkosto (sähköpostipalvelinten tavaan), eli eri palvelimille rekisteröityneet käyttäjät voivat olla yhteydessä toisiinsa vaikka eivät olisi rekisteröityneet samalle palvelimelle.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "API eli ohjelmointirajapinta on tietoliikenneprotokolla, jonka avulla ohjelmistokomponentit voivat olla yhteydessä keskenään. Mobilizon-API voi esimerkiksi tarjota kolmansien osapuolten ohjelmistotyökaluille yhteyden Mobilizon-palvelimiin erilaisten toimintojen, kuten tapahtumien julkaisemisen, suorittamiseksi automaattisesti ja etänä.",
"And {number} comments": "{number} kommenttia",
"Announcements and mentions notifications are always sent straight away.": "Ilmoitukset julkistuksista ja maininnoista lähetetään aina heti.",
"Anonymous participant": "Nimetön osallistuja",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonyymejä osallistujia pyydetään vahvistamaan osallistumisensa sähköpostitse.",
"Anonymous participations": "Anonyymit osallistujat",
"Any day": "Milloin vain",
"Anyone can join freely": "Kuka tahansa voi liittyä",
"Anyone can join freely": "Kaikki voivat liittyä",
"Anyone wanting to be a member from your group will be able to from your group page.": "Kaikki ryhmäsi jäsenet voivat liittyä ryhmäsi sivuilta.",
"Application": "Sovellus",
"Approve": "Hyväksy",
@ -88,8 +108,10 @@
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Haluatko varmasti keskeyttää tapahtuman luomisen? Kaikki muutokset menetetään.",
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Haluatko varmasti keskeyttää tapahtuman muokkaamisen? Kaikki muutokset menetetään.",
"Are you sure you want to cancel your participation at event \"{title}\"?": "Haluatko varmasti perua osallistumisesi tapahtumaan {title}?",
"Are you sure you want to delete this entire discussion?": "Oletko varma, että haluat poistaa koko tämän keskustelun?",
"Are you sure you want to delete this event? This action cannot be reverted.": "Haluatko varmasti poistaa tämän tapahtuman? Toimintoa ei voi perua.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Tapahtuman järjestäjä vahvistaa osallistumispyynnöt käsin, joten osallistumisesi on vahvistettu vasta sitten, kun saat vahvistuksesta kertovan sähköpostin.",
"Ask your instance admin to {enable_feature}.": "Kysy instanssisi ylläpitäjältä {enable_feature}.",
"Assigned to": "Yhdistetty",
"Atom feed for events and posts": "Tapahtumien ja julkaisujen Atom-syöte",
"Avatar": "Avatar",
@ -98,8 +120,11 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Sinun on ennen sisäänkirjautumista vahvistettava tilisi napsauttamalla siinä olevaa linkkiä.",
"Begins on": "Alkaa",
"Bold": "Lihavoitu",
"Breadcrumbs": "Leivänmurut",
"Browser notifications": "Selaimen ilmoitukset",
"By @{group}": "Tehnyt @{group}",
"By @{username}": "Tehnyt @{username}",
"By others": "Muilta",
"By {author}": "Tekijä {author}",
"By {group}": "Tekijä {group}",
"By {username} and {group}": "Tehnyt {username} ja {group}",
@ -120,6 +145,7 @@
"Change password": "Vaihda salasana",
"Change timezone": "Vaihda aikavyöhykettä",
"Check your inbox (and your junk mail folder).": "Tarkista sähköpostisi (myös roskapostikansio).",
"City or region": "Kaupunki tai alue",
"Clear": "Tyhjennä",
"Clear participation data for all events": "Poista kaikkien tapahtumien osallistumistiedot",
"Clear participation data for this event": "Poista tapahtuman osallistumistiedot",
@ -131,6 +157,7 @@
"Closed": "Suljettu",
"Comment deleted": "Kommentti poistettu",
"Comment from @{username} reported": "Käyttäjän @{username} kommentti raportoitu",
"Comment text can't be empty": "Kommentin teksti ei voi olla tyhjä",
"Comments": "Kommentit",
"Comments are closed for everybody else.": "Kommentit on suljettu muilta.",
"Comments have been closed.": "Kommentointi on pois käytöstä.",
@ -144,6 +171,8 @@
"Contact": "Yhteystieto",
"Continue editing": "Jatka muokkausta",
"Cookies and Local storage": "Evästeet ja paikallisesti tallennettavat tiedot",
"Copy URL to clipboard": "Kopioi URL leikepöydälle",
"Copy details to clipboard": "Kopioi yksityiskohdat leikepöydälle",
"Country": "Maa",
"Create": "Luo",
"Create a calc": "Luo taulukko",
@ -154,6 +183,7 @@
"Create a new group": "Luo uusi ryhmä",
"Create a new identity": "Luo uusi identiteetti",
"Create a new list": "Luo uusi luettelo",
"Create a new profile": "Luo uusi profiili",
"Create a pad": "Luo tekstiasiakirja",
"Create a videoconference": "Luo videokokous",
"Create a visioconference": "Luo videopuhelu",
@ -164,6 +194,7 @@
"Create my event": "Luo oma tapahtuma",
"Create my group": "Luo oma ryhmä",
"Create my profile": "Luo oma profiili",
"Create new links": "Luo uudet linkit",
"Create or join an group and start organizing with other people": "Luo ryhmä tai liity ryhmään ja ala järjestäytyä",
"Create resource": "Luo resurssi",
"Create the discussion": "Luo keskustelu",
@ -194,11 +225,13 @@
"Delete Event": "Poista tapahtuma",
"Delete account": "Poista tili",
"Delete conversation": "Poista keskustelu",
"Delete discussion": "Poista keskustelu",
"Delete event": "Poista tapahtuma",
"Delete everything": "Poista kaikki",
"Delete group": "Poista ryhmä",
"Delete my account": "Poista tilini",
"Delete post": "Poista julkaisu",
"Delete this discussion": "Poista tämä keskustelu",
"Delete this identity": "Poista tämä identiteetti",
"Delete your identity": "Poista oma identiteetti",
"Delete {eventTitle}": "Poista {eventTitle}",
@ -229,6 +262,7 @@
"Edit": "Muokkaa",
"Edit biography": "Muokkaa henkilökuvausta",
"Edit post": "Muokkaa julkaisua",
"Edit profile {profile}": "Muokkaa profiilia {profile}",
"Edited {ago}": "Muokattu {ago}",
"Eg: Stockholm, Dance, Chess…": "Esim. Helsinki, tanssi, shakki, …",
"Either on the {instance} instance or on another instance.": "Joko palvelimella {instance} tai toisella palvelimella.",
@ -248,17 +282,25 @@
"Enter your own terms. HTML tags allowed. Mobilizon.org's terms are provided as template.": "Syötä palvelimen käyttöehdot. HTML-merkinnät ovat sallittuja. Mallina voi käyttää Mobilizon.orgin käyttöehtoja.",
"Enter your own terms. HTML tags allowed. The {mobilizon_terms} are provided as template.": "Kirjoita omat ehdot. HTML-tunnisteet sallittuja. {mobilizon_terms} toimivat mallina.",
"Error": "Virhe",
"Error details copied!": "Virheen yksityiskohdat kopioitu!",
"Error message": "Virheilmoitus",
"Error stacktrace": "Virheen jäljitys",
"Error while changing email": "Virhe sähköpostiosoitetta vaihdettaessa",
"Error while communicating with the server.": "Virhe palvelinyhteydessä.",
"Error while loading the preview": "Esikatselun latauksessa tapahtui virhe",
"Error while login with {provider}. Retry or login another way.": "Virhe kirjauduttaessa {provider}-tilillä. Yritä uudelleen tai kirjaudu toista kautta.",
"Error while login with {provider}. This login provider doesn't exist.": "Virhe kirjauduttaessa {provider}-tilillä. Tätä sisäänkirjautumispalvelua ei ole olemassa.",
"Error while reporting group {groupTitle}": "Tapahtui virhe raportoidessa ryhmää {groupTitle}",
"Error while saving report.": "Virhe raportin tallennuksessa.",
"Error while subscribing to push notifications": "Virhe kun tilattiin push-ilmoitukset",
"Error while suspending group": "Ryhmän jäädytyksessä tapahtui virhe",
"Error while updating participation status inside this browser": "Osallistumistiedon päivittämisessä tapahtui virhe tässä selaimessa",
"Error while validating account": "Virhe tilin vahvistamisessa",
"Error while validating participation": "Virhe osallistumisen vahvistamisessa",
"Error while validating participation request": "Virhe osallistumispyyntöä vahvistettaessa",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "Eettinen vaihtoehto Facebookin tapahtumille, ryhmille ja sivuille. Mobilizon on työkalu, joka on <b>suunniteltu palvelemaan sinua</b>. Piste.",
"Event": "Tapahtuma",
"Event URL": "Tapahtuman URL",
"Event already passed": "Tapahtuma on jo mennyt",
"Event cancelled": "Tapahtuma peruttu",
"Event creation": "Tapahtuman luonti",
@ -270,6 +312,7 @@
"Event {eventTitle} deleted": "Tapahtuma {eventTitle} poistettu",
"Event {eventTitle} reported": "Tapahtuma (eventTitle} raportoitu",
"Events": "Tapahtumat",
"Events nearby": "Lähellä olevat tapahtumat",
"Events tagged with {tag}": "Tapahtumat tunnisteella {tag}",
"Everything": "Kaikki",
"Ex: mobilizon.fr": "Esim. mobilizon.fr",
@ -299,9 +342,11 @@
"From the {startDate} at {startTime} to the {endDate}": "Alkaa {startDate} klo {startTime} ja päättyy {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Alkaa {startDate} klo {startTime} ja päättyy {endDate} klo {endTime}",
"From the {startDate} to the {endDate}": "Alkaa {startDate} ja päättyy {endDate}",
"From yourself": "Sinulta",
"Gather ⋅ Organize ⋅ Mobilize": "Kokoonnu ⋅ Järjestä ⋅ Mobilisoi",
"General": "Yleinen",
"General information": "Yleiset tiedot",
"General settings": "Yleiset asetukset",
"Getting location": "Haetaan sijaintia",
"Getting there": "Reittiohjeet",
"Glossary": "Sanasto",
@ -312,6 +357,8 @@
"Group Followers": "Ryhmän seuraajat",
"Group List": "Ryhmäluettelo",
"Group Members": "Ryhmän jäsenet",
"Group URL": "Ryhmän URL",
"Group activity": "Ryhmän muutokset",
"Group address": "Ryhmän osoite",
"Group display name": "Ryhmän näyttönimi",
"Group full name": "Ryhmän koko nimi",
@ -339,8 +386,11 @@
"I participate": "Osallistun",
"I want to allow people to participate without an account.": "Osallistuminen ilman käyttäjätiliä sallittu.",
"I want to approve every participation request": "Haluan hyväksyä kaikki osallistumispyynnöt",
"I've been mentionned in a comment under an event": "Minut on mainittu tapahtuman kommentissa",
"I've been mentionned in a group discussion": "Minut on mainittu ryhmäkeskustelussa",
"ICS feed for events": "Tapahtumien ICS-syöte",
"ICS/WebCal Feed": "ICS/WebCal-syöte",
"Identities": "Identiteetit",
"Identity {displayName} created": "Identiteetti {displayName} luotu",
"Identity {displayName} deleted": "Identiteetti {displayName} poistettu",
"Identity {displayName} updated": "Identiteetti {displayName} päivitetty",
@ -351,6 +401,7 @@
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Jos sinulta kysytään federoitua käyttäjätunnustasi, se koostuu käyttäjätunnuksestasi ja palvelimestasi. Esimerkiksi ensimmäisellä profiilillasi se on:",
"If you have opted for manual validation of participants, Mobilizon will send you an email to inform you of new participations to be processed. You can choose the frequency of these notifications below.": "Jos olet valinnut osallistujien manuaalisen vahvistuksen, Mobilizon lähettää sinulle viestin uusista osallistujista jotka voit vahvistaa. Voit alla valita näiden viestien tiheyden.",
"If you want, you may send a message to the event organizer here.": "Tästä voit halutessasi lähettää tapahtuman järjestäjälle viestin.",
"Ignore": "Sivuuta",
"Impossible to login, your email or password seems incorrect.": "Sisäänkirjautuminen ei onnistu, sähköposti tai salasana on väärin.",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "Seuraavassa sovellus tarkoittaa Mobilizon-tiimin tai kolmannen osapuolen toimittamaa ohjelmaa, jonka kautta palvelinta käytetään.",
"In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "Huomaathan, että tämä ohjelma ei ole (vielä) kokonaan valmis. Lue lisää: {onBlog}.",
@ -370,12 +421,14 @@
"Instance Terms URL": "Pälvelimen käyttöehtojen URL",
"Instance administrator": "Palvelimen ylläpitäjä",
"Instance configuration": "Palvelimen asetukset",
"Instance feeds": "Instanssin syötteet",
"Instance languages": "Instanssin kielet",
"Instance rules": "Palvelimen säännöt",
"Instance settings": "Palvelimen asetukset",
"Instances": "Palvelimet",
"Instances following you": "Instanssit, jotka seuraavat sinua",
"Instances you follow": "Instanssit, joita seuraat",
"Interact with a remote content": "Vuorovaikuta ulkoisen sisällön kanssa",
"Invite a new member": "Kutsu uusi jäsen",
"Invite member": "Kutsu jäsen",
"Invited": "Kutsuttu",
@ -383,6 +436,7 @@
"Italic": "Kursivoitu",
"Join <b>{instance}</b>, a Mobilizon instance": "Liity Mobilizon-palvelimelle <b>{instance}</b>",
"Join group": "Liity ryhmään",
"Join group {group}": "Liity ryhmään {group}",
"Join {instance}, a Mobilizon instance": "Liity Mobilizon-palvelimelle {instance}",
"Keep the entire conversation about a specific topic together on a single page.": "Pidä kaikki aiheeseen liittyvä keskustelu samassa paikassa.",
"Key words": "Avainsanat",
@ -390,6 +444,7 @@
"Last IP adress": "Edellinen IP-osoite",
"Last group created": "Viimeisin luotu ryhmä",
"Last published event": "Viimeisin julkaistu tapahtuma",
"Last published events": "Viimeksi julkaistut tapahtumat",
"Last sign-in": "Edellinen sisäänkirjautuminen",
"Last week": "Viime viikko",
"Latest posts": "Viimeiset julkaisut",
@ -408,6 +463,7 @@
"Limited number of places": "Paikkoja rajoitettu määrä",
"List title": "Luettelon otsikko",
"Load more": "Lataa lisää",
"Load more activities": "Lataa lisää toimintaa",
"Loading comments…": "Ladataan kommentteja…",
"Local": "Paikallinen",
"Locality": "Sijainti",
@ -427,6 +483,7 @@
"Mark as resolved": "Merkitse ratkaistuksi",
"Member": "Jäsen",
"Members": "Jäsenet",
"Mentions": "Maininnat",
"Message": "Viesti",
"Mobilizon": "Mobilizon",
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon on federoituva verkosto. Tätä tapahtumasivua voi käyttää myös toiselta palvelimelta.",
@ -448,6 +505,7 @@
"Moderator": "Moderaattori",
"Move": "Siirrä",
"Move \"{resourceName}\"": "Siirrä ”{resourceName}”",
"Move resource to the root folder": "Siirrä resurssi juurikansioon",
"Move resource to {folder}": "Siirrä resurssi kansioon {folder}",
"My account": "Oma tili",
"My events": "Omat tapahtumat",
@ -462,6 +520,7 @@
"New members": "Uudet jäsenet",
"New note": "Uusi merkintä",
"New password": "Uusi salasana",
"New post": "Uusi viesti",
"New profile": "Uusi profiili",
"Next": "Seuraava",
"Next month": "Ensi kuussa",
@ -477,22 +536,30 @@
"No events found": "Tapahtumia ei löytynyt",
"No follower matches the filters": "Ei suodatinta vastaavia seuraajia",
"No group found": "Ryhmää ei löytynyt",
"No group matches the filters": "Yksikään ryhmä ei vastaa suodattimia",
"No groups found": "Ryhmiä ei löytynyt",
"No information": "Ei tietoa",
"No instance follows your instance yet.": "Mikään palvelin ei vielä seuraa tätä palvelinta.",
"No instance to approve|Approve instance|Approve {number} instances": "Ei hyväksyttäviä palvelimia|Hyväksy palvelin|Hyväksy {number} palvelinta",
"No instance to reject|Reject instance|Reject {number} instances": "Ei hylättäviä palvelimia|Hylkää palvelin|Hylkää {number} palvelinta",
"No instance to remove|Remove instance|Remove {number} instances": "Ei poistettavia palvelimia|Poista palvelin|Poista {number} palvelinta",
"No languages found": "Kieliä ei löytynyt",
"No member matches the filters": "Ei suodattimia vastaavia jäseniä",
"No members found": "Käyttäjiä ei löydy",
"No memberships found": "Jäsenyyksiä ei löytynyt",
"No message": "Ei viestiä",
"No moderation logs yet": "Moderointilokia ei vielä ole",
"No more activity to display.": "Toimintaa ei ole enää näytettävänä.",
"No notification settings yet": "Ei vielä ilmoitusasetuksia",
"No one is participating|One person participating|{going} people participating": "Ei osallistujia|Yksi osallistuja|{going} osallistujaa",
"No ongoing todos": "Ei keskeneräisiä tehtäviä",
"No open reports yet": "Avoimia raportteja ei vielä ole",
"No organized events found": "Järjestettyjä tapahtumia ei löydy",
"No organized events listed": "Ei järjestettyjä tapahtumia listattuna",
"No participant matches the filters": "Ei suodattimia vastaavia osallistujia",
"No participant to approve|Approve participant|Approve {number} participants": "Ei osallistujia hyväksyttäväksi|Hyväksy osallistuja|Hyväksy {number} osallistujaa",
"No participant to reject|Reject participant|Reject {number} participants": "Ei osallistujia hylättäväksi|Hylkää osallistuja|Hylkää {number} osallistujaa",
"No participations listed": "Ei osallistumisia listattuna",
"No posts found": "Viestejä ei löytynyt",
"No posts yet": "Ei julkaisuja vielä",
"No preferences yet": "Valintoja ei ole vielä tehty",
@ -514,8 +581,10 @@
"Nothing to see here": "Ei mitään nähtävää",
"Notification before the event": "Ilmoitus ennen tapahtumaa",
"Notification on the day of the event": "Ilmoitus tapahtumapäivänä",
"Notification settings": "Ilmoitusasetukset",
"Notifications": "Ilmoitukset",
"Notifications for manually approved participations to an event": "Ilmoitukset tapahtuman käsin hyväksytyistä osallistumisista",
"Notify participants": "Ilmoita osallistujille",
"Now, create your first profile:": "Luo seuraavaksi ensimmäinen profiilisi:",
"Number of places": "Paikkojen määrä",
"OK": "OK",
@ -524,6 +593,7 @@
"On {date} ending at {endTime}": "{date}, päättyy {endTime}",
"On {date} from {startTime} to {endTime}": "{date} klo {startTime}{endTime}",
"On {date} starting at {startTime}": "{date} klo {startTime}",
"On {instance} and other federated instances": "Instanssilla {instance} ja muilla federoiduilla instansseilla",
"One person is going": "Kukaan ei ole menossa | Yksi henkilö menossa | {approved} henkilöä menossa",
"Ongoing tasks": "Meneillään olevat tehtävät",
"Only accessible through link": "Pääsy vain linkistä",
@ -535,16 +605,20 @@
"Only group members can access discussions": "Vain ryhmän jäsenet voivat päästä keskusteluihin",
"Only group moderators can create, edit and delete posts.": "Vain ryhmän moderaattorit voivat luode, muokata ja poistaa viestejä.",
"Open": "Avoin",
"Open a topic on our forum": "Luo uusi aihe foorumillamme",
"Open an issue on our bug tracker (advanced users)": "Avaa vikailmoitus virheenjäljittimessämme (edistyneet käyttäjät)",
"Opened reports": "Avatut raportit",
"Or": "Tai",
"Organize and take action, freely": "Organisoidu ja toimi vapaasti",
"Organized": "Järjestetty",
"Organized by": "Järjestäjä",
"Organized by you": "Järjestämäsi",
"Organized by {name}": "Järjestää {name}",
"Organizer": "Järjestäjä",
"Organizer notifications": "Järjestäjän ilmoitukset",
"Organizers": "Järjestäjät",
"Other": "Muu",
"Other actions": "Muut toiminnat",
"Other notification options:": "Muut ilmoitusvalinnat:",
"Other software may also support this.": "Myös muut ohjelmat voivat tukea tätä.",
"Otherwise this identity will just be removed from the group administrators.": "Muussa tapauksessa tämä identiteetti vain poistetaan ryhmän ylläpitäjistä.",
@ -569,11 +643,13 @@
"Password reset": "Salasanan palautus",
"Past events": "Menneet tapahtumat",
"Pending": "Odottaa",
"Personal feeds": "Henkilökohtaiset syötteet",
"Pick": "Valitse",
"Pick a group": "Valitse ryhmä",
"Pick a profile or a group": "Valitse profiili tai ryhmä",
"Pick an identity": "Valitse identiteetti",
"Pick an instance": "Valitse instanssi",
"Please add as many details as possible to help identify the problem.": "Laita niin monta yksityiskohtaa kuin mahdollista, jotta voimme tunnistaa ongelman.",
"Please check your spam folder if you didn't receive the email.": "Jos et saanut sähköpostia, tarkista roskapostikansio.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Jos epäilet tätä virheeksi, ota yhteyttä tämän palvelimen Mobilizon-ylläpitäjään.",
"Please do not use it in any real way.": "Älä käytä todellisiin tarkoituksiin.",
@ -597,6 +673,8 @@
"Privacy policy": "Tietosuojakäytäntö",
"Private event": "Yksityistapahtuma",
"Private feeds": "Yksityissyötteet",
"Profile": "Profiili",
"Profile feeds": "Profiilin syöte",
"Profiles": "Profiilit",
"Profiles and federation": "Profiilit ja federointi",
"Promote": "Nosta",
@ -607,18 +685,22 @@
"Public feeds": "Julkiset syötteet",
"Public iCal Feed": "Julkinen iCal-syöte",
"Public page": "Julkinen sivu",
"Public preview": "Julkinen esikatselu",
"Publication date": "Julkaisupäivä",
"Publish": "Julkaise",
"Published events": "Julkaistut tapahtumat",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Julkaistuissa tapahtumissa <b>{comments}</b> kommenttia ja <b>{participations}</b> vahvistettua osallistumista",
"Push": "Push",
"RSS/Atom Feed": "RSS/Atom-syöte",
"Radius": "Säde",
"Read Framasofts statement of intent on the Framablog": "Lue Framasoftin päämääräjulkilausuma Framablogista",
"Recap every week": "Muistutus joka viikko",
"Receive one email for each activity": "Vastaanota yksi sähköposti jokaista toimintoa kohden",
"Receive one email per request": "Vastaanota sähköposti jokaisesta pyynnöstä",
"Redirecting to content…": "Ohjataan sisältöön…",
"Redirecting to event…": "Siirrytään tapahtumaan…",
"Refresh profile": "Päivitä profiili",
"Regenerate new links": "Luo linkit uudelleen",
"Region": "Alue",
"Register": "Rekisteröidy",
"Register an account on Mobilizon!": "Rekisteröi Mobilizon-tili!",
@ -674,17 +756,22 @@
"Searching…": "Haetaan…",
"Search…": "Hae…",
"Select a language": "Valitse kieli",
"Select a radius": "Valitse säde",
"Select a timezone": "Valitse aikavyöhyke",
"Select languages": "Valitse kielet",
"Select the activities for which you wish to receive an email or a push notification.": "Valitse toiminnat, joista haluat saada sähköposti- tai push-ilmoitukset.",
"Send email": "Lähetä sähköposti",
"Send me an email to reset my password": "Lähetä salasananpalautusviesti",
"Send me the confirmation email once again": "Lähetä vahvistussähköposti uudelleen",
"Send notification e-mails": "Lähetä ilmoitusten sähköpostit",
"Send the confirmation email again": "Lähetä vahvistussähköposti uudelleen",
"Send the report": "Lähetä raportti",
"Set an URL to a page with your own privacy policy.": "Aseta osoitteeksi oman tietosuojakäytäntösivun osoite.",
"Set an URL to a page with your own terms.": "Aseta palvelimen käyttöehdot sisältävän sivun URL.",
"Settings": "Asetukset",
"Share": "Jaa",
"Share this event": "Jaa tapahtuma",
"Share this group": "Jaa tämä ryhmä",
"Short bio": "Lyhyt kuvaus",
"Show map": "Näytä kartta",
"Show remaining number of places": "Näytä vapaana olevien paikkojen määrä",
@ -705,6 +792,7 @@
"Suspend group": "Estä ryhmä",
"Suspended": "Estetty",
"Task lists": "Tehtäväluettelot",
"Technical details": "Tekniset tiedot",
"Tentative": "Alustava",
"Tentative: Will be confirmed later": "Alustava: vahvistetaan myöhemmin",
"Terms": "Käyttöehdot",
@ -725,10 +813,20 @@
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "Tapahtuman järjestäjä hyväksyy osallistujat käsin. Koska olet päättänyt osallistua ilman tiliä, kerro, miksi haluat osallistua tapahtumaan.",
"The event title will be ellipsed.": "Tapahtuman otsikkoa lyhennetään.",
"The event will show as attributed to this group.": "Tapahtuma näytetään liitettynä tähän ryhmään.",
"The event will show as attributed to this profile.": "Tapahtuma näkyy liitettynä tähän profiiliin.",
"The event will show as attributed to your personal profile.": "Tapahtuma näytetään liitettynä henkilökohtaiseen profiiliisi.",
"The event will show the group as organizer.": "Ryhmä näkyy tapahtuman järjestäjänä.",
"The event {event} was created by {profile}.": "Tapahtuman {event} loi {profile}.",
"The event {event} was deleted by {profile}.": "Tapahtuman {event} poisti {profile}.",
"The event {event} was updated by {profile}.": "Tapahtumaa {event} päivitti {profile}.",
"The events you created are not shown here.": "Luomiasi tapahtumia ei näytetä tässä.",
"The group can now be joined by anyone.": "Ryhmään voivat nyt liittyä kaikki.",
"The group can now only be joined with an invite.": "Ryhmään voi nyt liittyä vain kutsusta.",
"The group will be publicly listed in search results and may be suggested in the explore section. Only public informations will be shown on it's page.": "Ryhmä näkyy julkisissa hakutuloksissa, ja sitä voidaan esitellä esittelyosiossa. Esittelysivulla näytetään vain julkisia tietoja.",
"The group's avatar was changed.": "Ryhmän avatar muutettiin.",
"The group's banner was changed.": "Ryhmän banneri muutettiin.",
"The group's physical address was changed.": "Ryhmän osoite muutettiin.",
"The group's short description was changed.": "Ryhmän lyhyt kuvaus muutettiin.",
"The instance administrator is the person or entity that runs this Mobilizon instance.": "Palvelimen ylläpitäjä on tämän Mobilizon-palvelimen toiminnasta vastaava henkilö tai taho.",
"The member was removed from the group {group}": "Jäsen poistettiin ryhmästä {group}",
"The new email doesn't seem to be valid": "Uusi sähköpostiosoite ei kelpaa",
@ -739,14 +837,22 @@
"The page you're looking for doesn't exist.": "Etsimääsi sivua ei ole olemassa.",
"The password provided is invalid": "Annettu salasana ei kelpaa",
"The password was successfully changed": "Salasanan vaihto onnistui",
"The post {post} was created by {profile}.": "Viestin {post} loi {profile}.",
"The post {post} was deleted by {profile}.": "Viestin {post} poisti {profile}.",
"The post {post} was updated by {profile}.": "Viestiä {post} päivitti {profile}.",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Raportti lähetetään oman palvelimesi moderaattoreille. Alla voit kertoa, miksi raportoit sisällöstä.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "Valittu kuva on liian iso. Valitse tiedosto, joka on pienempi kuin {size}.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Virheen tekniset yksityiskohdat voivat auttaa kehittäjiä ratkaisemaan ongelman helpommin. Lisää ne palautteeseesi, kiitos.",
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.": "Käyttäjätiliä, jolla yrität kirjautua, ei ole vielä vahvistettu. Tarkista sähköpostilaatikkosi ja tarvittaessa roskapostikansiosi.",
"The {default_privacy_policy} will be used. They will be translated in the user's language.": "Käytössä on {default_privacy_policy}. Ne käännetään käyttäjän valitsemalle kielelle.",
"The {default_terms} will be used. They will be translated in the user's language.": "{default_terms} ovat käytössä. Ne käännetään käyttäjän kielelle.",
"There are {participants} participants.": "Osallistujia on {participants}.",
"There is no activity yet. Start doing some things to see activity appear here.": "Toimintaa ei ole vielä täällä. Ala tekemään jotain, jotta näet toimintaa tapahtuvan täällä.",
"There will be no way to recover your data.": "Tietoja ei voi palauttaa millään tavalla.",
"There's no discussions yet": "Keskustelua ei vielä ole",
"These events may interest you": "Nämä tapahtumat saattavat kiinnostaa sinua",
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Nämä syötteet sisältävät tapahtumadataa tapahtumille, joissa jokin profiileistasi on osallistuja tai luoja. Sinun tulisi pitää nämä yksityisinä. Voit löytää syötteet tietyille profiileille jokaisen profiilin sivuilta.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Nämä syötteet sisältävät tapahtumadataa tapahtumille, joissa tämä tietty profiili on osallistuja tai luoja. Sinun pitäisi pitää nämä yksityisinä. Voit löytää kaikkien profiiliesi syötteet ilmoitusasetuksista.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Anonyymi osallistuminen on sallittua tällä Mobilizon-palvelimella ja tähän tapahtumaan, mutta osallistuminen on vahvistettava sähköpostitse.",
"This URL is not supported": "Osoite ei ole tuettu",
"This email is already registered as participant for this event": "Sähköpostiosoite on jo kirjattu tapahtuman osallistujiin",
@ -754,11 +860,13 @@
"This event has been cancelled.": "Tapahtuma on peruttu.",
"This event is accessible only through it's link. Be careful where you post this link.": "Tapahtumasivulle on pääsy vain linkin kautta. Ole tarkkana, missä julkaiset linkin.",
"This group doesn't have a description yet.": "Ryhmällä ei ole vielä kuvausta.",
"This group is accessible only through it's link. Be careful where you post this link.": "Tähän ryhmään pääsee vain linkin kautta. Ole varovainen minne jaat tämän linkin.",
"This group is invite-only": "Tämä ryhmä on vain kutsun saaneille",
"This identifier is unique to your profile. It allows others to find you.": "Tämä on yksilöllinen tunniste profiiliisi. Sen avulla muut voivat löytää sinut.",
"This identity is not a member of any group.": "Tämä identiteetti ei ole jäsenenä missään ryhmässä.",
"This information is saved only on your computer. Click for details": "Nämä tiedot tallennetaan vain omalle tietokoneellesi. Katso lisätietoja napsauttamalla",
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "{protocol}-protokollan ansiosta tämä palvelin on vaivattomasti yhteydessä muihin ({interconnect}).",
"This instance hasn't got push notifications enabled.": "Tämä instanssi ei ole ottanut käyttöön push-ilmoituksia.",
"This instance isn't opened to registrations, but you can register on other instances.": "Tälle palvelimelle ei voi rekisteröityä, mutta voit rekisteröityä muille palvelimille.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Tämä palvelin, <b>{instanceName} ({domain})</b>, sisältää profiilisi joten älä unohda sen nimeä.",
"This is a demonstration site to test Mobilizon.": "Tämä on koekäyttöön tarkoitettu Mobilizonin esittelysivu.",
@ -787,24 +895,32 @@
"Today": "Tänään",
"Tomorrow": "Huomenna",
"Transfer to {outsideDomain}": "Siirry osoitteeseen {outsideDomain}",
"Triggered profile refreshment": "Profiilin päivitys aloitettu",
"Type": "Tyyppi",
"Type or select a date…": "Syötä tai valitse päivämäärä…",
"URL": "URL",
"URL copied to clipboard": "Osoite kopioitu leikepöydälle",
"Unable to copy to clipboard": "Ei pystytty kopioimaan leikepöydälle",
"Unable to create the group. One of the pictures may be too heavy.": "Ryhmää ei pystytty luomaan. Jokin kuvista voi olla liian iso.",
"Unable to create the profile. The avatar picture may be too heavy.": "Profiilia ei voitu luoda. Profiilikuva saataa olla liian raskas.",
"Unable to detect timezone.": "Aikavyöhykettä ei pystytty tunnistamaan.",
"Unable to load event for participation. The error details are provided below:": "Tapahtumaa ei voi ladata osallistumista varten. Tarkemmat tiedot virheestä:",
"Unable to save your participation in this browser.": "Osallistumistasi ei voi tallentaa tässä selaimessa.",
"Unable to update the profile. The avatar picture may be too heavy.": "Profiilia ei voitu päivittää. Profiilikuva saattaa olla liian raskas.",
"Unfortunately, this instance isn't opened to registrations": "Valitettavasti tälle palvelimelle ei voi rekisteröityä",
"Unfortunately, your participation request was rejected by the organizers.": "Ikävä kyllä järjestäjät hylkäsivät osallistumispyyntösi.",
"Unknown": "Tuntematon",
"Unknown actor": "Tuntematon tekijä",
"Unknown error.": "Tuntematon virhe.",
"Unknown value for the openness setting.": "Tuntematon arvo avoimuuden asetukselle.",
"Unsaved changes": "Tallentamattomia muutoksia",
"Unset group": "Ryhmää ei asetettu",
"Unsubscribe to browser push notifications": "Peruuta selaimen push-ilmoitukset",
"Unsuspend": "Poista esto",
"Upcoming": "Tulossa",
"Upcoming events": "Tulevat tapahtumat",
"Update": "Päivitä",
"Update app": "Päivitä sovellus",
"Update event {name}": "Päivitä tapahtumaa {name}",
"Update group": "Päivitä ryhmä",
"Update my event": "Päivitä omaa tapahtumaa",
@ -813,6 +929,7 @@
"Uploaded media size": "Lähetetyn median koko",
"Use my location": "Käytä sijaintiani",
"User": "Käyttäjä",
"User settings": "Käyttäjän asetukset",
"Username": "Käyttäjänimi",
"Users": "Käyttäjät",
"View a reply": "|Näytä vastaus|Näytä {totalReplies} vastausta",
@ -825,13 +942,20 @@
"View all upcoming events": "Näytä kaikki tulevat tapahtumat",
"View event page": "Näytä tapahtumasivu",
"View everything": "Näytä kaikki",
"View less": "Katso vähemmän",
"View more": "Katso enemmän",
"View page on {hostname} (in a new window)": "Näytä sivu palvelimella {hostname} (uudessa ikkunassa)",
"Visibility was set to an unknown value.": "Näkyvyys asetettiin tuntemattomaan arvoon.",
"Visibility was set to private.": "Näkyvyys asetettiin yksityiseksi.",
"Visibility was set to public.": "Näkyvyys asetettiin julkiseksi.",
"Visible everywhere on the web": "Näkyy kaikkialle verkossa",
"Visible everywhere on the web (public)": "Näkyy kaikkialla verkossa (julkinen)",
"Waiting for organization team approval.": "Odottaa järjestäjien hyväksyntää.",
"Waiting list": "Odotuslista",
"Warning": "Varoitus",
"We asked professional designers to help us develop our vision for Mobilizon. We took time to study the {digital_habits} in order to understand the features they need to gather, organize, and mobilize.": "Pyysimme ammattisuunnittelijoita auttamaan Mobilizon-visiomme kehittämisessä. Perehdyimme {digital_habits}, jotta ymmärtäisimme, mitä ominaisuuksia he tarvitsevat kokoontumisessaan, organisoitumisessaan ja mobilisoitumisessaan.",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Emme voineet tallentaa osallistumistasi tässä selaimessa. Ei syytä huoleen, olet vahvistanut osallistumisesi, mutta se ei tallentunut tähän selaimeen jostain teknisestä syystä.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Parannamme tätä ohjelmistoa palautteesi takia. Jotta saamme tietää tästä ongelmasta, kaksi mahdollisuutta (molemmat vaativat ikävä kyllä käyttäjätunnuksen luomista):",
"We just sent an email to {email}": "Lähetimme juuri sähköpostia osoitteeseen {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Aikavyöhyketiedon avulla saat tapahtumailmoitukset oikeaan aikaan.",
"We want to develop a <b>digital common</b>, that everyone can make their own, which respects <b>privacy and activism by design</b>.": "Haluamme kehittää <b>digitaalista yhteisomaisuutta</b>, josta jokainen voi tehdä omanlaisensa ja joka kunnioittaa <b>yksityisyyttä ja aktivismia ihan luonnostaan</b>.",
@ -842,6 +966,7 @@
"We'll use your timezone settings to send a recap of the morning of the event.": "Tapahtumapäivän aamuna lähetetään muistutus, ja siihen hyödynnetään aikavyöhykeasetusta.",
"Website": "Verkkosivu",
"Website / URL": "Verkkosivu/URL",
"Weekly email summary": "Viikottainen koostesähköposti",
"Welcome back {username}!": "Tervetuloa takaisin, {username}!",
"Welcome back!": "Tervetuloa takaisin!",
"Welcome on your administration panel": "Tervetuloa hallintapaneeliin",
@ -853,14 +978,20 @@
"Who published {number} events": "{number} julkaistua tapahtumaa",
"Why create an account?": "Miksi kannattaa tehdä tunnus?",
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Sallimme osallistumisesi näyttämisen ja hallinnoinnin tapahtumasivulla tällä laitteella. Älä valitse, jos käytät julkista laitetta.",
"Within {number} kilometers of {place}": "|Yhden kilometrin sisällä paikasta {place}|{number} kilometrin päässä paikasta {place}",
"World map": "Maailmankartta",
"Write something…": "Kirjoita jotain…",
"Yesterday": "Eilen",
"You accepted the invitation to join the group.": "Hyväksyit liittymiskutsun ryhmään.",
"You added the member {member}.": "Lisäsit käyttäjän {member}.",
"You and one other person are going to this event": "Vain sinä olet menossa tähän tapahtumaan | Vain sinä ja yksi sinun lisäksesi on menossa tähän tapahtumaan | Sinä ja {approved} muuta henkilöä ovat menossa tähän tapahtumaan.",
"You archived the discussion {discussion}.": "Arkistoit keskustelun {discussion}.",
"You are already a participant of this event.": "Olet jo tapahtuman osallistuja.",
"You are already logged-in.": "Olet jo kirjautunut sisään.",
"You are not an administrator for this group.": "Et ole ryhmän ylläpitäjä.",
"You are not part of any group": "Et ole yhdenkään ryhmän jäsen",
"You are not part of any group.": "Et kuulu mihinkään ryhmään.",
"You are offline": "Ei verkkoyhteyttä",
"You are participating in this event anonymously": "Osallistut tapahtumaan anonyymisti",
"You are participating in this event anonymously but didn't confirm participation": "Osallistut tapahtumaan anonyymisti, mutta et ole vahvistanut osallistumistasi",
"You can add tags by hitting the Enter key or by adding a comma": "Voit lisätä tunnisteita painamalla enteriä tai lisäämällä pilkun",
@ -870,9 +1001,26 @@
"You can't change your password because you are registered through {provider}.": "Et voi vaihtaa salasanaa, koska olet rekisteröitynyt palvelussa {provider}.",
"You can't remove your last identity.": "Et voi poistaa viimeistä identiteettiäsi.",
"You can't reset your password because you use a 3rd-party auth provider to login.": "Et voi palauttaa salasanaa, koska käytät kirjautumiseen ulkopuolista tunnistautumispalvelua.",
"You can't use push notifications in this browser.": "Et voi käyttää push-ilmoituksia tässä selaimessa.",
"You changed your email or password": "Muutit sähköpostisi tai salasanasi",
"You created the discussion {discussion}.": "Loit keskustelun {discussion}.",
"You created the event {event}.": "Loit tapahtuman {event}.",
"You created the folder {resource}.": "Loit kansion {resource}.",
"You created the group {group}.": "Loit ryhmän {group}.",
"You created the post {post}.": "Loit viestin {post}.",
"You created the resource {resource}.": "Loit resurssin {resource}.",
"You deleted the discussion {discussion}.": "Poistit keskustelun {discussion}.",
"You deleted the event {event}.": "Poistit tapahtuman {event}.",
"You deleted the folder {resource}.": "Poistit kansio {resource}.",
"You deleted the post {post}.": "Poistit viestin {post}.",
"You deleted the resource {resource}.": "Poistit resurssin {resource}.",
"You demoted the member {member} to an unknown role.": "Alensit käyttäjän {member} tuntemattomaan rooliin.",
"You demoted {member} to moderator.": "Alensit käyttäjän {member} roolin moderaattoriksi.",
"You demoted {member} to simple member.": "Alensit käyttäjän {member} roolin yksinkertaiseksi käyttäjäksi.",
"You didn't create or join any event yet": "Et ole vielä luonut tapahtumaa tai liittynyt tapahtumaan",
"You didn't create or join any event yet.": "Et ole vielä luonut tapahtumaa tai liittynyt mihinkään tapahtumaan.",
"You don't follow any instances yet.": "Et seuraa vielä yhtäkään palvelinta.",
"You excluded member {member}.": "Jätit pois käyttäjän {member}.",
"You have been disconnected": "Yhteytesi on katkaistu",
"You have been invited by {invitedBy} to the following group:": "{invitedBy} on kutsunut sinut seuraaviin ryhmiin:",
"You have been removed from this group's members.": "Sinut on poistettu ryhmän jäsenistä.",
@ -880,17 +1028,44 @@
"You have one event in {days} days.": "Sinulla ei ole tapahtumia seuraavien {days} päivän aikana | Sinulla on yksi tapahtuma seuraavien {days} päivän aikana | Sinulla on {count} tapahtumaa seuraavien {days} päivän aikana",
"You have one event today.": "Sinulla ei ole tapahtumia tänään | Sinulla on yksi tapahtuma tänään | Sinulla on {count} tapahtumaa tänään",
"You have one event tomorrow.": "Sinulla ei ole tapahtumia huomenna | Sinulla on yksi tapahtuma huomenna | Sinulla on {count} tapahtumaa huomenna",
"You invited {member}.": "Kutsuit käyttäjän {member}.",
"You may also ask to {resend_confirmation_email}.": "Voit myös pyytää {resend_confirmation_email}.",
"You may clear all participation information for this device with the buttons below.": "Alla olevalla painikkeella voit poistaa kaikki tällä laitteella olevat osallistumistiedot.",
"You may now close this window, or {return_to_event}.": "Voit sulkea ikkunan tai {return_to_event}.",
"You may now close this window.": "Voit nyt sulkea tämän ikkunan.",
"You may show some members as contacts.": "Voit näyttää jotkut käyttäjät yhteyshenkilöinä.",
"You moved the folder {resource} into {new_path}.": "Siirsit kansion {resource} polkuun {new_parh}.",
"You moved the folder {resource} to the root folder.": "Siirsit kansion {resource} juurikansioon.",
"You moved the resource {resource} into {new_path}.": "Siirsit resurssin {resource} polkuun {new_path].",
"You moved the resource {resource} to the root folder.": "Siirsit resurssin {resource} juurikansioon.",
"You need to create the group before you create an event.": "Luo ryhmä, jotta voit luoda tapahtuman.",
"You need to login.": "Kirjaudu sisään.",
"You posted a comment on the event {event}.": "Lähetit kommentin tapahtumaan {event}.",
"You promoted the member {member} to an unknown role.": "Ylensit käyttäjän {member} tuntemattomaan rooliin.",
"You promoted {member} to administrator.": "Ylensit käyttäjän {member} ylläpitäjäksi.",
"You promoted {member} to moderator.": "Ylensit käyttäjän {member} moderaattoriksi.",
"You renamed the discussion from {old_discussion} to {discussion}.": "Nimesit keskustelun uudelleen nmestä {old_discussion} nimeksi {discussion}.",
"You renamed the folder from {old_resource_title} to {resource}.": "Muutit kansion nimestä {old_resource_title} nimeksi {resource}.",
"You renamed the resource from {old_resource_title} to {resource}.": "Nimesit resurssin uudelleen nimestä {old_resource_title} nimeksi {resource}.",
"You replied to a comment on the event {event}.": "Vastasit kommenttiin tapahtumassa {event}.",
"You replied to the discussion {discussion}.": "Vastasit keskusteluun {discussion}.",
"You requested to join the group.": "Pyysit liittymistäsi ryhmään.",
"You updated the event {event}.": "Päivitit tapahtumaa {event}.",
"You updated the group {group}.": "Päivitit ryhmän {group}.",
"You updated the member {member}.": "Päivitit käyttäjää {member}.",
"You updated the post {post}.": "Päivitit viestiä {post}.",
"You were demoted to an unknown role by {profile}.": "{profile} alensi roolisi tuntemattomaan rooliin.",
"You were demoted to moderator by {profile}.": "{profile} alensi roolisi moderaattoriksi.",
"You were demoted to simple member by {profile}.": "{profile} alensi roolisi yksinkertaiseksi käyttäjäksi.",
"You were promoted to administrator by {profile}.": "{profile} ylensi sinut ylläpitäjäksi.",
"You were promoted to an unknown role by {profile}.": "{profile} ylensi sinut tuntemattomaan rooliin.",
"You were promoted to moderator by {profile}.": "{profile} ylensi sinut moderaattoriksi.",
"You will be able to add an avatar and set other options in your account settings.": "Voit lisätä profiilikuvan ja antaa muita tietoja tunnuksen asetuksissasi.",
"You will be redirected to the original instance": "Sinut ohjataan alkuperäiselle palvelimelle",
"You will find here all the events you have created or of which you are a participant.": "Täällä ovat kaikki luomasi tapahtumat sekä tapahtumat, joihin olet osallistumassa.",
"You wish to participate to the following event": "Haluat osallistua seuraavaan tapahtumaan",
"You'll get a weekly recap every Monday for upcoming events, if you have any.": "Saat joka maanantai muistutuksen viikon mahdollisista tulevista tapahtumista.",
"You'll need to change the URLs where there were previously entered.": "Verkko-osoitteet tulee muuttaa sieltä mistä ne oli aiemmin syötetty.",
"You'll need to transmit the group URL so people may access the group's profile.": "Ryhmän profiilin näkeminen edellyttää sitä, että jaat ryhmän osoitetta ihmisille.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Sinun täytyy jakaa linkki ryhmään, jotta käyttäjät voivat päästä ryhmän profiilisivulle. Ryhmä ei näy Mobilizonin haussa tai tavallisilla hakukoneilla.",
"You'll receive a confirmation email.": "Saat sähköpostiin vahvistuksen.",
@ -898,6 +1073,7 @@
"Your account has been validated": "Tilisi on vahvistettu",
"Your account is being validated": "Tiliäsi vahvistetaan",
"Your account is nearly ready, {username}": "Tilisi on melkein valmis, {username}",
"Your city or region and the radius will only be used to suggest you events nearby. The event radius will consider the administrative center of the area.": "Kaupunkisi tai alueesi ja säde käytetään vain ehdottamaan sinulle tapahtumia läheltä. Tapahtuman säde ottaa huomioon alueen hallinnollisen keskustan.",
"Your current email is {email}. You use it to log in.": "Nykyinen sähköpostiosoitteesi on {email}. Kirjaudu sisään sillä.",
"Your email": "Sähköpostiosoitteesi",
"Your email address was automatically set based on your {provider} account.": "Sähköpostiosoitteesi asetettiin automaattisesti {provider}-tilisi perusteella.",
@ -923,6 +1099,7 @@
"Your timezone is currently set to {timezone}.": "Nykyinen aikavyöhykkeesi on {timezone}.",
"Your timezone was detected as {timezone}.": "Aikavyöhykkeeksesi tunnistettiin {timezone}.",
"Your timezone {timezone} isn't supported.": "Aikavyöhykkeesi {timezone} ei ole tuettu.",
"Your upcoming events": "Tulevat tapahtumasi",
"[This comment has been deleted by it's author]": "[Kommentin kirjoittaja on poistanut kommentin]",
"[This comment has been deleted]": "[Kommentti on poistettu]",
"[deleted]": "[poistettu]",
@ -939,6 +1116,7 @@
"default Mobilizon terms": "Mobilizonin oletuskäyttöehdot",
"digital habits of activists": "aktivistien digitaalisiin käytäntöihin",
"e.g. 10 Rue Jangot": "esim. Hämeenkatu 10",
"enable the feature": "salli ominaisuus",
"explore the events": "tutustua tapahtumiin",
"explore the groups": "tutustua ryhmiin",
"firstDayOfWeek": "0",
@ -959,26 +1137,72 @@
"with another identity…": "toisella identiteetillä…",
"{approved} / {total} seats": "{approved} / {total} paikkaa",
"{available}/{capacity} available places": "Ei paikkoja jäljellä|{available}/{capacity} paikkaa jäljellä",
"{count} km": "{count} km",
"{count} members": "Ei jäseniä|Yksi jäsen|{count} jäsentä",
"{count} participants": "Ei osallistujia vielä | Yksi osallistuja | {count} osallistujaa",
"{count} requests waiting": "{count} pyyntöä odottamassa",
"{count} team members": "tiimissä {count} jäsentä",
"{folder} - Resources": "{folder} - Resurssit",
"{group} activity timeline": "{group} toiminnan aikajana",
"{group} events": "{group} tapahtumat",
"{group}'s events": "Ryhmän {group} tapahtumat",
"{instanceName} is an instance of the {mobilizon} software.": "{instanceName} on {mobilizon}-ohjelmaa käyttävä palvelin.",
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} on {mobilizon_link} -instanssi, yhteisön luoma vapaa ohjelmisto.",
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} varmistaa sitä käyttävien ihmisten {respect}. Koska {source}, kuka tahansa voi tarkastaa sen ja varmistua sen läpinäkyvyydestä.",
"{member} accepted the invitation to join the group.": "{member} hyväksyi liittymiskutsun.",
"{member} rejected the invitation to join the group.": "{member} hylkäsi kutsun ryhmään liittymiseen.",
"{member} requested to join the group.": "{member} pyysi liittymistä ryhmään.",
"{member} was invited by {profile}.": "Käyttäjän {member} kutsui {profile}.",
"{moderator} added a note on {report}": "{moderator} lisäsi huomautuksen: {report}",
"{moderator} closed {report}": "{moderator} sulki: {report}",
"{moderator} deleted an event named \"{title}\"": "{moderator} poisti tapahtuman ”{title)”",
"{moderator} has deleted a comment from {author}": "{moderator} poisti kommentin henkilöltä {author}",
"{moderator} has deleted a comment from {author} under the event {event}": "{moderator} poisti kommentin henkilöltä {author} joka liittyi tapahtumaan {event}",
"{moderator} has deleted user {user}": "{moderator} on poistanut käyttäjän {user}",
"{moderator} has done an unknown action": "{moderator} teki jotain tuntematonta",
"{moderator} has unsuspended group {profile}": "{moderator} poisti ryhmän jäädytyksen {profile}",
"{moderator} has unsuspended profile {profile}": "{moderator} on poistanut profiilin {profile} eston",
"{moderator} marked {report} as resolved": "{moderator} merkitsi ratkaistuksi: {report}",
"{moderator} reopened {report}": "{moderator} avasi uudelleen: {report}",
"{moderator} suspended group {profile}": "{moderator} jäädytti ryhmän {profile}",
"{moderator} suspended profile {profile}": "{moderator} esti profiilin {profile}",
"{nb} km": "{nb} km",
"{number} members": "{number} jäsentä",
"{number} memberships": "{number} jäsenyyttä",
"{number} organized events": "Ei järjestettyjä tapahtumia|Yksi järjestetty tapahtuma|{number} järjestettyä tapahtumaa",
"{number} participations": "Ei osallistumisia|Yksi osallistuminen|{number} osallistumista",
"{number} posts": "Ei julkaisuja|Yksi julkaisu|{number} julkaisua",
"{old_group_name} was renamed to {group}.": "{old_group_name} nimettiin uudelleen ryhmäksi {group}.",
"{profile} (by default)": "{profile} (oletuksena)",
"{profile} added the member {member}.": "{profile} lisäsi käyttäjän {member}.",
"{profile} archived the discussion {discussion}.": "{profile} arkistoi keskustelun {discussion}.",
"{profile} created the discussion {discussion}.": "{profile} loi keskustelun {discussion}.",
"{profile} created the folder {resource}.": "{profile} loi kansion {resource}.",
"{profile} created the group {group}.": "{profile} loi ryhmän {group}.",
"{profile} created the resource {resource}.": "{profile} loi resurssin {resource}.",
"{profile} deleted the discussion {discussion}.": "{profile} poisti keskustelun {discussion}.",
"{profile} deleted the folder {resource}.": "{profile} poisti kansion {resource}.",
"{profile} deleted the resource {resource}.": "{profile} poisti resurssin {resource}.",
"{profile} demoted {member} to an unknown role.": "{profile} alensi käyttäjän {member} tuntemattomaan rooliin.",
"{profile} demoted {member} to moderator.": "{profile} alensi käyttäjän {member} moderaattoriksi.",
"{profile} demoted {member} to simple member.": "{profile} alensi käytäjän {member} yksinkertaiseksi käyttäjäksi.",
"{profile} excluded member {member}.": "{profile} jätti pois käyttäjän {member}.",
"{profile} moved the folder {resource} into {new_path}.": "{profile} siirsi kansion {resource} polkuun {new_path}.",
"{profile} moved the folder {resource} to the root folder.": "{profile} siirsi kansion {resource} juurikansioon.",
"{profile} moved the resource {resource} into {new_path}.": "{profile} siirsi resurssin {resource} polkuun {new_path}.",
"{profile} moved the resource {resource} to the root folder.": "{profile} siirsi resurssin {resource} juurikansioon.",
"{profile} posted a comment on the event {event}.": "{profile} lähetti kommentin tapahtumaan {event}.",
"{profile} promoted {member} to administrator.": "{proflle} ylensi käyttäjän {member} ylläpitäjäksi.",
"{profile} promoted {member} to an unknown role.": "{proflle} ylensi käyttäjän {member} tuntemattomaan rooliin.",
"{profile} promoted {member} to moderator.": "{profile} ylensi käyttäjän {member} moderaattoriksi.",
"{profile} quit the group.": "{profile} jätti ryhmän.",
"{profile} renamed the discussion from {old_discussion} to {discussion}.": "{profile} nimesi keskustelun uudelleen nimestä {old_discussion} nimeksi {discussion}.",
"{profile} renamed the folder from {old_resource_title} to {resource}.": "{profile} nimesi uudelleen kansion nimestä {old_resource_title} nimeksi {resource}.",
"{profile} renamed the resource from {old_resource_title} to {resource}.": "{profile} nimesi resurssin nimestä {old_resource_title} nimeksi {resource}.",
"{profile} replied to a comment on the event {event}.": "{profile} vastasi kommenttiin tapahtumassa {event}.",
"{profile} replied to the discussion {discussion}.": "{profile} vastasi keskusteluun {discussion}.",
"{profile} updated the group {group}.": "{profile} päivitti ryhmää {group}.",
"{profile} updated the member {member}.": "{profile} päivitti käyttäjää {member}.",
"{title} ({count} todos)": "{title} ({count} tehtävää)",
"{username} was invited to {group}": "{username} kutsuttiin ryhmään {group}",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Mobilizon-tekijät {date} Valmistusaineina Elixir, Phoenix, VueJS sekä rakkaus ja aika",

View File

@ -99,7 +99,7 @@
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Êtes-vous certain⋅e de vouloir <b>supprimer</b> cet événement ? Cette action n'est pas réversible. Vous voulez peut-être engager la discussion avec le créateur de l'événement ou bien modifier son événement à la place.",
"Are you sure you want to <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et <b>toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites</b>.",
"Are you sure you want to <b>suspend</b> this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Comme ce groupe provient de l'instance {instance}, cela supprimera seulement les membres locaux et supprimera les données locales, et rejettera également toutes les données futures.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Étes-vous certain⋅e de vouloir annuler la création de l'événement ? Vous allez perdre toutes vos modifications.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Êtes-vous certain⋅e de vouloir annuler la création de l'événement ? Vous allez perdre toutes vos modifications.",
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Êtes-vous certain⋅e de vouloir annuler la modification de l'événement ? Vous allez perdre toutes vos modifications.",
"Are you sure you want to cancel your participation at event \"{title}\"?": "Êtes-vous certain⋅e de vouloir annuler votre participation à l'événement « {title} » ?",
"Are you sure you want to delete this entire discussion?": "Êtes-vous certain⋅e de vouloir supprimer l'entièreté de cette discussion ?",
@ -1154,5 +1154,84 @@
"Ask your instance admin to {enable_feature}.": "Demandez à l'administrateur⋅ice de votre instance d'{enable_feature}.",
"Event URL": "URL de l'événement",
"Copy URL to clipboard": "Copier l'URL dans le presse-papiers",
"Group URL": "URL du groupe"
"Group URL": "URL du groupe",
"View less": "Voir moins",
"View more": "Voir plus",
"Breadcrumbs": "Fil d'Ariane",
"Other actions": "Autres actions",
"Only group moderators can create, edit and delete events.": "Seule⋅s les modérateur⋅ices de groupe peuvent créer, éditer et supprimer des événements.",
"+ Create a post": "+ Créer un billet",
"Edited {relative_time} ago": "Édité il y a {relative_time}",
"Members-only post": "Billet reservé aux membres",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Ce billet est accessible uniquement aux membres. Vous y avez accès à des fins de modération car vous êtes modérateur⋅ice de l'instance.",
"Find or add an element": "Trouver ou ajouter un élément",
"e.g. Accessibility, Twitch, PeerTube": "par ex. Accessibilité, Framadate, PeerTube",
"Add new…": "Ajouter un nouvel élément…",
"No results for {search}": "Pas de résultats pour {search}",
"Wheelchair accessibility": "Accessibilité aux fauteuils roulants",
"Whether the event is accessible with a wheelchair": "Si l'événement est accessible avec un fauteuil roulant",
"Not accessible with a wheelchair": "Non accessible avec un fauteuil roulant",
"Partially accessible with a wheelchair": "Partiellement accessible avec un fauteuil roulant",
"Fully accessible with a wheelchair": "Entièrement accessible avec un fauteuil roulant",
"YouTube replay": "Replay sur YouTube",
"The URL where the event live can be watched again after it has ended": "L'URL où le direct de l'événement peut être visionné à nouveau une fois terminé",
"Twitch replay": "Replay sur Twitch",
"PeerTube replay": "Replay sur PeerTube",
"PeerTube live": "Direct sur PeerTube",
"The URL where the event can be watched live": "L'URL où l'événement peut être visionné en direct",
"Twitch live": "Direct sur Twitch",
"YouTube live": "Direct sur YouTube",
"Event metadata": "Métadonnées de l'événement",
"Framadate poll": "Sondage Framadate",
"The URL of a poll where the choice for the event date is happening": "L'URL d'un sondage où la date de l'événement doit être choisie",
"View account on {hostname} (in a new window)": "Voir le compte sur {hostname} (dans une nouvelle fenêtre)",
"Twitter account": "Compte Twitter",
"A twitter account handle to follow for event updates": "Un compte sur Twitter à suivre pour les mises à jour de l'événement",
"Fediverse account": "Compte fediverse",
"A fediverse account URL to follow for event updates": "Un compte sur le fediverse à suivre pour les mises à jour de l'événement",
"Element title": "Titre de l'élement",
"Element value": "Valeur de l'élement",
"Subtitles": "Sous-titres",
"Whether the event live video is subtitled": "Si le direct vidéo de l'événement est sous-titré",
"The event live video contains subtitles": "Le direct vidéo de l'événement contient des sous-titres",
"The event live video does not contain subtitles": "Le direct vidéo de l'événement ne contient pas de sous-titres",
"Sign Language": "Langue des signes",
"Whether the event is interpreted in sign language": "Si l'événement est interprété en langue des signes",
"The event has a sign language interpreter": "L'événement a un interprète en langue des signes",
"The event hasn't got a sign language interpreter": "L'événement n'a pas d'interprète en langue des signes",
"Online ticketing": "Billetterie en ligne",
"An URL to an external ticketing platform": "Une URL vers une plateforme de billetterie externe",
"Price sheet": "Feuille des prix",
"A link to a page presenting the price options": "Un lien vers une page présentant la tarification",
"Integrate this event with 3rd-party tools and show metadata for the event.": "Intégrer cet événement avec des outils tiers et afficher des métadonnées pour l'événement.",
"This URL doesn't seem to be valid": "Cette URL ne semble pas être valide",
"Schedule": "Programme",
"A link to a page presenting the event schedule": "Un lien vers une page présentant le programme de l'événement",
"Accessibility": "Accessibilité",
"Live": "Direct",
"Replay": "Rattrapage",
"Tools": "Outils",
"Social": "Social",
"Details": "Détails",
"Booking": "Réservations",
"Filter by profile or group name": "Filter par nom du profil ou du groupe",
"Filter by name": "Filtrer par nom",
"Redirecting in progress…": "Redirection en cours…",
"Zoom in": "Zoomer",
"Zoom out": "Dézoomer",
"Show me where I am": "Afficher ma position",
"Video Conference": "Visio-conférence",
"Jisti Meet": "Jitsi Meet",
"The Jitsi Meet video teleconference URL": "L'URL de visio-conférence Jitsi Meet",
"Zoom": "Zoom",
"The Zoom video teleconference URL": "L'URL de visio-conférence Zoom",
"Microsoft Teams": "Microsoft Teams",
"The Microsoft Teams video teleconference URL": "L'URL de visio-conférence Microsoft Teams",
"Google Meet": "Google Meet",
"The Google Meet video teleconference URL": "L'URL de visio-conférence Google Meet",
"Big Blue Button": "Big Blue Button",
"The Big Blue Button video teleconference URL": "L'URL de visio-conférence Big Blue Button",
"Etherpad notes": "Notes sur Etherpad",
"The URL of a pad where notes are being taken collaboratively": "L'URL d'un pad où les notes sont prises collaborativement",
"https://mensuel.framapad.org/p/some-secret-token": "https://mensuel.framapad.org/p/un-jeton-secret"
}

View File

@ -4,6 +4,7 @@
"(this folder)": "(am pasgan seo)",
"(this link)": "(an ceangal seo)",
"+ Add a resource": "+ Cuir goireas ris",
"+ Create a post": "+ Cruthaich post",
"+ Create an event": "+ Cruthaich tachartas",
"+ Post a public message": "+ Postaich teachdaireachd phoblach",
"+ Start a discussion": "+ Tòisich air deasbad",
@ -14,13 +15,24 @@
"@{username}'s follow request was accepted": "Chaidh gabhail ris an t-iarrtas leantainn aig @{username}",
"@{username}'s follow request was rejected": "Chaidh an t-iarrtas leantainn aig @{username} a dhiùltadh",
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "S e faidhle beag a th ann am briosgaid sa bheil fiosrachadh s a thèid a chur dhan choimpiutair agad nuair a thadhlas tu air làrach-lìn. Nuair a thadhlas tu air an làrach-lìn a-rithist, aithnichidh an làrach ud am brabhsair agad leis a bhriosgaid. Gabhaidh roghainnean cleachdaiche is fiosrachadh eile a stòradh ann am briosgaid. S urrainn dhut am brabhsair agad a rèiteachadh ach an diùlt e gach briosgaid. Gidheadh, dhfhaoidte nach obraich a h-uile gleus no seirbheis aig làrach-lìn mar bu chòir an uairsin. Obraichidh an stòras ionadail air an aon dòigh ach faodar barrachd dàta a chumail ann.",
"A discussion has been created or updated": "Chaidh deasbad a chruthachadh no ùrachadh",
"A federated software": "Bathar-bog co-naisgte",
"A fediverse account URL to follow for event updates": "URL cunntais co-shaoghail airson leantainn air naidheachdan tachartais",
"A link to a page presenting the event schedule": "Ceangal gu duilleag a sheallas sgeideal an tachartais",
"A link to a page presenting the price options": "Ceangal gu duilleag a sheallas roghainnean nam prìsean",
"A member has been updated": "Chaidh ball ùrachadh",
"A member requested to join one of my groups": "Dhiarr cuideigin ballrachd ann am fear dhe na buidhnean agam",
"A new version is available.": "Tha tionndadh ùr ri fhaighinn.",
"A place for your code of conduct, rules or guidelines. You can use HTML tags.": "Seo àite airson còd-giùlain, riaghailtean no comharra-treòrachaidh. S urrainn dhut tagaichean HTML a chleachdadh.",
"A place to explain who you are and the things that set your instance apart. You can use HTML tags.": "Seo àite airson cò thusa agus dè a tha sònraichte mun ionstans agad a mhìneachadh. S urrainn dhut tagaichean HTML a chleachdadh.",
"A place to publish something to the whole world, your community or just your group members.": "Seo àite airson rudan fhoillseachadh dhan t-saoghal gu lèir, dhan choimhearsnachd agad no do bhuill do bhuidhinn a-mhàin.",
"A place to store links to documents or resources of any type.": "Seo àite airson ceanglaichean gu sgrìobhainnean no goireasan sam bith eile a stòradh.",
"A post has been published": "Chaidh post fhoillseachadh",
"A post has been updated": "Chaidh post ùrachadh",
"A practical tool": "Acainn prataigeach",
"A resource has been created or updated": "Chaidh goireas a chruthachadh no ùrachadh",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Seo facal-suaicheantais goirid airson duilleag-dhachaigh an ionstans agad. S e “Cruinnich ⋅ Cuir air dòigh ⋅ Iomair” a tha sa bhun-roghainn",
"A twitter account handle to follow for event updates": "Làimhsichear cunntas Twitter airson leantainn air naidheachdan tachartais",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Inneal saor beusail airson cruinneachadh, cur air dòigh is iomairt a tha furasta cleachdadh.",
"A validation email was sent to {email}": "Chaidh post-d dearbhaidh a chur gu {email}",
"API": "API",
@ -33,10 +45,12 @@
"About {instance}": "Mu {instance}",
"Accept": "Gabh ris",
"Accepted": "Air a ghabhail ris",
"Accessibility": "So-ruigsinneachd",
"Accessible only to members": "Chan fhaod ach buill inntrigeadh",
"Accessible through link": "Gabhaidh inntrigeadh le ceangal",
"Account": "Cunntas",
"Actions": "Gnìomhan",
"Activate browser push notifications": "Gnìomhaich brathan putaidh a bhrabhsair",
"Activated": "An gnìomh",
"Active": "Gnìomhach",
"Activity": "Gnìomhachd",
@ -50,6 +64,7 @@
"Add a todo": "Cuir rud ri dhèanamh ris",
"Add an address": "Cuir seòladh ris",
"Add an instance": "Cuir ionstans ris",
"Add new…": "Cuir fear ùr ris…",
"Add some tags": "Cuir tagaichean ris",
"Add to my calendar": "Cuir ris a mhìosachan agam",
"Additional comments": "Beachdan a bharrachd",
@ -62,11 +77,21 @@
"All the places have already been taken": "Chan eil àite saor air fhàgail",
"Allow all comments from users with accounts": "Ceadaich a h-uile beachd o chleachdaichean air an clàradh a-steach",
"Allow registrations": "Ceadaich clàradh",
"An URL to an external ticketing platform": "URL tu ùrlar ticeadan air an taobh a-muigh",
"An error has occured while refreshing the page.": "Thachair mearachd fhad s a bha sinn ag ath-nuadhachadh na duilleige.",
"An error has occured. Sorry about that. You may try to reload the page.": "Thachair mearachd. Tha sinn duilich mu dhèidhinn. Feuch is ath-luchdaich an duilleag.",
"An ethical alternative": "Roghainn bheusail",
"An event I'm going to has been updated": "Chaidh tachartas dhan dèid mi ùrachadh",
"An event I'm going to has posted an announcement": "Phostaidh tachartas dhan dèid mi brath-fios",
"An event I'm organizing has a new comment": "Tha beachd ùr ri tachartas a tha mi a cur air dòigh",
"An event I'm organizing has a new participation": "Tha com-pàirteachadh ùr ann an tachartas a tha mi a cur air dòigh",
"An event I'm organizing has a new pending participation": "Tha com-pàirteachadh ùr a feitheamh ann an tachartas a tha mi a cur air dòigh",
"An event from one of my groups has been published": "Dhfhoillsich fear dhe na buidhnean agam tachartas",
"An event from one of my groups has been updated or deleted": "Chaidh tachartas aig fear dhe na buidhnean agam ùrachadh no a sguabadh às",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "S e tionndadh dhen bhathar-bhog Mobilizon a chaidh a stàladh s a tha a ruith air frithealaiche a th ann an ionstans. S urrainn do dhuine sam bith ionstans a ruith le {mobilizon_software} no le aplacaidean co-naisgte eile, sin an “co-shaoghal”. Is {instance_name} ainm an ionstans seo. S e lìonra de dhiomadh ionstans co-naisgte (coltach ri frithealaichean puist-d) a th ann am Mobilizon. S urrainn do chleachdaichean a chlàraich le ionstansan eadar-dhealaichte conaltradh ri chèile ged nach do chlàraich iad air an on ionstans.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "S e pròtacal conaltraidh a th ann an “application programming interface” no “API” a leigeas le co-phàirtean bathair-bhog conaltradh ri chèile. Mar eisimpleir, s urrainn dhan API aig Mobilizon leigeil le innealan bathair-bhog threas-phàrtaidhean gun dèan iad conaltradh le ionstansan de Mhobilizon airson gnìomhan sònraichte a ghabhail, can postadh thachartasan às do leth gu fèin-obrachail is gu cèin.",
"And {number} comments": "Agus {number} beachd",
"Announcements and mentions notifications are always sent straight away.": "Thèid brathan mu bhrathan-fios is iomraidhean a chur sa bhad an-còmhnaidh.",
"Anonymous participant": "Freastalaiche gun ainm",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Thèid iarraidh air freastalaichean gun ainm gun dearbh iad an com-pàirteachadh air a phost-d.",
"Anonymous participations": "Com-pàirteachaidhean gun ainm",
@ -86,6 +111,7 @@
"Are you sure you want to delete this entire discussion?": "A bheil thu cinnteach gu bheil thu airson an deasbad seo a sguabadh às uile gu lèir?",
"Are you sure you want to delete this event? This action cannot be reverted.": "A bheil thu cinnteach gu bheil thu airson an tachartas seo a sguabadh às? Cha ghabh seo a neo-dhèanamh.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Thagh eagraiche an tachartais gun dearbh iad cò ghabhas pàirt a làimh, mar sin cha bhi do chom-pàirteachadh air a dhearbhadh mus fhaigh thu post-d a dhinnseas gun do ghabh iad ris.",
"Ask your instance admin to {enable_feature}.": "Iarr air rianaire an ionstans agad {enable_feature}.",
"Assigned to": "Air iomruineadh dha",
"Atom feed for events and posts": "Inbhir Atom dha na tachartasan is postaichean",
"Avatar": "Avatar",
@ -94,6 +120,9 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Mus urrainn dhut clàradh a-steach, feumaidh tu briogadh air a cheangal na broinn gus an cunntas agad a dhearbhadh.",
"Begins on": "Tòisichidh e aig",
"Bold": "Trom",
"Booking": "Bucadh",
"Breadcrumbs": "Breadcrumbs",
"Browser notifications": "Brathan a bhrabhsair",
"By @{group}": "Le @{group}",
"By @{username}": "Le @{username}",
"By others": "Le daoine eile",
@ -122,10 +151,10 @@
"Click to upload": "Briog airson luchdadh suas",
"Close": "Dùin",
"Close comments for all (except for admins)": "Dùin na beachdan dhan a h-uile duine (ach rianairean)",
"Events nearby": "Tachartasan am fagas",
"Closed": "Dùinte",
"Comment deleted": "Chaidh am beachd a sguabadh às",
"Comment from @{username} reported": "Chaidh gearan a dhèanamh mu bheachd le @{username}",
"Comment text can't be empty": "Chan fhaod teacsa a bheachd a bhith bàn",
"Comments": "Beachdan",
"Comments are closed for everybody else.": "Tha na beachdan dùinte dhan a h-uile duine eile.",
"Confirm my participation": "Dearbh an com-pàirteachadh agam",
@ -137,6 +166,7 @@
"Contact": "Fios thugainn",
"Continue editing": "Lean air an deasachadh",
"Cookies and Local storage": "Briosgaidean is an stòras ionadail",
"Copy URL to clipboard": "Cuir lethbhreac dhen URL air an stòr-bhòrd",
"Copy details to clipboard": "Cuir lethbhreac dhen mion-fhiosrachadh air an stòr-bhòrd",
"Country": "Dùthaich",
"Create": "Cruthaich",
@ -147,6 +177,7 @@
"Create a new group": "Cruthaich buidheann ùr",
"Create a new identity": "Cruthaich dearbh-aithne ùr",
"Create a new list": "Cruthaich liosta ùr",
"Create a new profile": "Cruthaich pròifil ùr",
"Create a pad": "Cruthaich pada",
"Create a videoconference": "Cruthaich coinneamh video",
"Create an account": "Cruthaich cunntas",
@ -155,6 +186,7 @@
"Create my event": "Cruthaich an tachartas agam",
"Create my group": "Cruthaich am buidheann agam",
"Create my profile": "Cruthaich a phròifil agam",
"Create new links": "Cruthaich ceanglaichean ùra",
"Create resource": "Cruthaich goireas",
"Create the discussion": "Cruthaich an deasbad",
"Create to-do lists for all the tasks you need to do, assign them and set due dates.": "Cruthaich liostaichean de rudan ri dhèanamh dha na saothraichean uile a tha romhaibh, iomruin iad is suidhich cinn-latha orra.",
@ -173,9 +205,9 @@
"Date and time settings": "Roghainnean a chinn-là s an ama",
"Date parameters": "Paramadairean a chinn-là",
"Decline": "Diùlt",
"Default": "Tùsail",
"Default Mobilizon privacy policy": "Poileasaidh prìobhaideachd Mhobilizon thùsail",
"Default Mobilizon terms": "Teirmichean Mhobilizon tùsail",
"Default": "Bun-roghainn",
"Default Mobilizon privacy policy": "Poileasaidh prìobhaideachd Mhobilizon bhunaiteach",
"Default Mobilizon terms": "Teirmichean Mhobilizon bunaiteach",
"Delete": "Sguab às",
"Delete Comment": "Sguab às am beachd",
"Delete Event": "Sguab às an tachartas",
@ -198,6 +230,7 @@
"Deleting your Mobilizon account": "A sguabadh às an cunntas Mobilizon agad",
"Demote": "Ìslich",
"Description": "Tuairisgeul",
"Details": "Mion-fhiosrachadh",
"Didn't receive the instructions?": "Nach dfhuair thu an stiùireadh?",
"Disabled": "À comas",
"Discussions": "Deasbadan",
@ -215,12 +248,16 @@
"Duplicate": "Dùblaich",
"Edit": "Deasaich",
"Edit post": "Deasaich am post",
"Edit profile {profile}": "Deasaich pròifil {profile}",
"Edited {ago}": "Air a dheasachadh {ago}",
"Edited {relative_time} ago": "Chaidh a dheasachadh {relative_time} air ais",
"Eg: Stockholm, Dance, Chess…": "M.e.: Steòrnabhagh, Cèilidh, Spòrs…",
"Either on the {instance} instance or on another instance.": "Air an ionstans {instance} no air ionstans eile.",
"Either the account is already validated, either the validation token is incorrect.": "Chaidh an cunntas a dhearbhadh mo thràth no chan eil tòcan an dearbhaidh mar bu chòir.",
"Either the email has already been changed, either the validation token is incorrect.": "Chaidh am post-d atharrachadh mu thràth no chan eil tòcan an dearbhaidh mar bu chòir.",
"Either the participation request has already been validated, either the validation token is incorrect.": "Chaidh an t-iarrtas air com-pàirteachadh a dhearbhadh mu thràth no chan eil tòcan a chom-pàirteachaidh mar bu chòir.",
"Element title": "Tiotal na h-eileamaid",
"Element value": "Luach na h-eileamaid",
"Email": "Post-d",
"Email address": "Seòladh puist-d",
"Email notifications": "Brathan puist-d",
@ -235,23 +272,30 @@
"Error message": "Teachdaireachd na mearachd",
"Error stacktrace": "Stacktrace na mearachd",
"Error while changing email": "Mearachd le atharrachadh a phuist-d",
"Error while loading the preview": "Mearachd le luchdadh an ro-sheallaidh",
"Error while login with {provider}. Retry or login another way.": "Mearachd a clàradh a-steach le {provider}. Feuch ris a-rithist no clàradh a-steach air dòigh eile.",
"Error while login with {provider}. This login provider doesn't exist.": "Mearachd a clàradh a-steach le {provider}. Chan eil an solaraiche clàraidh a-steach seo ann.",
"Error while reporting group {groupTitle}": "Tachair mearachd leis a ghearan mun bhuidheann {groupTitle}",
"Error while subscribing to push notifications": "Mearachd le fo-sgrìobhadh air brathan putaidh",
"Error while suspending group": "Mearachd le cur à rèim a bhuidhinn",
"Error while updating participation status inside this browser": "Mearachd le ùrachadh staid mun ghabhail pàirt am broinn a bhrabhsair seo",
"Error while validating account": "Mearachd le dearbhadh a chunntais",
"Error while validating participation request": "Mearachd le dearbhadh an iarrtais air com-pàirteachadh",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "S e roghainn bheusail seach tachartasan, buidhnean is duilleagan Facebook a th ann am Mobilizon is <b>chaidh a dhealbhadh air do shon-sa</b>. Sin agad e.",
"Event": "Tachartas",
"Event URL": "URL an tachartais",
"Event already passed": "Tha an tachartas seachad mu thràth",
"Event cancelled": "Chaidh an tachartas a chur gu neoini",
"Event creation": "Cruthachadh tachartais",
"Event edition": "Deasachadh tachartais",
"Event list": "Liosta nan tachartasan",
"Event metadata": "Meata-dàta an tachartais",
"Event page settings": "Roghainnean duilleag an tachartais",
"Event to be confirmed": "Tachartas ri dearbhadh",
"Event {eventTitle} deleted": "Chaidh an tachartas {eventTitle} a sguabadh às",
"Event {eventTitle} reported": "Chaidh gearan a dhèanamh mun tachartas {eventTitle}",
"Events": "Tachartasan",
"Events nearby": "Tachartasan am fagas",
"Events tagged with {tag}": "Tachartasan le taga {tag} riutha",
"Everything": "A h-uile rud",
"Ex: mobilizon.fr": "Can: mobilizon.fr",
@ -262,10 +306,14 @@
"Featured events": "Tachartasan brosnaichte",
"Federated Group Name": "Ainm co-naisgte a bhuidhinn",
"Federation": "Co-nasgadh",
"Fediverse account": "Cunntas co-shaoghail",
"Fetch more": "Faigh barrachd dheth",
"Filter by name": "Criathraich a-rèir ainm",
"Filter by profile or group name": "Criathraich a-rèir ainm pròifil no buidhinn",
"Find an address": "Lorg seòladh",
"Find an instance": "Lorg ionstans",
"Find another instance": "Lorg ionstans eile",
"Find or add an element": "Lorg no cuir eileamaid ris",
"Follower": "Neach-leantainn",
"Followers": "Luchd-leantainn",
"Followers will receive new public events and posts.": "Gheibh an luchd-leantainn tachartasan is postaichean poblach.",
@ -274,20 +322,26 @@
"For instance: London, Taekwondo, Architecture…": "Mar eisimpleir: Glaschu, Camanachd, Ailtireachd…",
"Forgot your password ?": "Na dhìochuimhnich thu am facal-faire agad?",
"Forgot your password?": "Na dhìochuimhnich thu am facal-faire agad?",
"Framadate poll": "Cunntas-bheachd Framadate",
"From the {startDate} at {startTime} to the {endDate}": "O {startDate} aig {startTime} gu {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "O {startDate} aig {startTime} gu {endDate} aig {endTime}",
"From the {startDate} to the {endDate}": "O {startDate} gu {endDate}",
"From yourself": "Uat fhèin",
"Fully accessible with a wheelchair": "Gabhaidh a h-uile càil a ruigsinn le cathair-chuibhle",
"Gather ⋅ Organize ⋅ Mobilize": "Cruinnich ⋅ Cuir air dòigh ⋅ Iomair",
"General": "Coitcheann",
"General information": "Fiosrachadh coitcheann",
"General settings": "Roghainnean coitcheann",
"Getting location": "A faighinn an ionaid",
"Getting there": "Mar a gheibh thu ann",
"Glossary": "Briathrachan",
"Go": "Siuthad",
"Go to the event page": "Tadhail air duilleag an tachartais",
"Group": "Buidheann",
"Group Followers": "Luchd-leantainn a bhuidhinn",
"Group Members": "Buill dhen bhuidheann",
"Group URL": "URL a bhuidhinn",
"Group activity": "Gnìomhachd buidhinn",
"Group address": "Seòladh a bhuidhinn",
"Group display name": "Ainm-taisbeanaidh a bhuidhinn",
"Group name": "Ainm a bhuidhinn",
@ -313,8 +367,11 @@
"I participate": "Gabhaidh mi pàirt",
"I want to allow people to participate without an account.": "Tha mi airson ceadachadh gun gabh daoine pàirt às aonais cunntais.",
"I want to approve every participation request": "Tha mi airson aontachadh ris a h-uile iarrtas air com-pàirteachadh",
"I've been mentionned in a comment under an event": "Chaidh iomradh a thoirt orm ann am beachd fo thachartas",
"I've been mentionned in a group discussion": "Chaidh iomradh a thoirt orm ann an deasbad buidhinn",
"ICS feed for events": "Inbhir ICS dha na tachartasan",
"ICS/WebCal Feed": "Inbhir ICS/WebCal",
"Identities": "Dearbh-aithnean",
"Identity {displayName} created": "Chaidh an dearbh-aithne {displayName} a chruthachadh",
"Identity {displayName} deleted": "Chaidh an dearbh-aithne {displayName} a sguabadh às",
"Identity {displayName} updated": "Chaidh an dearbh-aithne {displayName} ùrachadh",
@ -324,7 +381,8 @@
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Ma thèid do dhearbh-aithne co-naisgte iarraidh ort, cuiridh tu an t-ainm-cleachdaiche s an ionstans agad ri chèile dha. Mar eisimpleir, seo an dearbh-aithne co-naisgte aig a chiad phròifil agad:",
"If you have opted for manual validation of participants, Mobilizon will send you an email to inform you of new participations to be processed. You can choose the frequency of these notifications below.": "Thagh thu gu bheil thu airson na com-pàirtichean a dhearbhadh a làimh. Cuiridh Mobilizon post-d thugad nuair a bhios com-pàirtichean ùra ri an dearbhadh ann. S urrainn dhut taghadh gu h-ìosal dè cho tric s a gheibh thu na brathan sin.",
"If you want, you may send a message to the event organizer here.": "Faodaidh tu teachdaireachd a chur gu eagraiche an tachartais an-seo ma thogras tu.",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "Sa cho-theacs a leanas, s e bathar-bog a th ann an aplacaid a chleachdas tu airson eadar-ghnìomhan a ghabhail leis an ionstans agad s a tha ga sholar le sgioba Mobilizon no le tread-phàrtaidh.",
"Ignore": "Leig seachad",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "Sa cho-theacs a leanas, s e bathar-bog a th ann an aplacaid a chleachdas tu airson conaltradh leis an ionstans agad s a tha ga sholar le sgioba Mobilizon no le treas-phàrtaidh.",
"Instance": "Ionstans",
"Instance Long Description": "Tuairisgeul fada an ionstans",
"Instance Name": "Ainm an ionstans",
@ -339,12 +397,15 @@
"Instance Terms URL": "URL teirmichean an ionstans",
"Instance administrator": "Rianaire ionstans",
"Instance configuration": "Rèiteachadh an ionstans",
"Instance feeds": "Inbhirean an ionstans",
"Instance languages": "Cànain an ionstans",
"Instance rules": "Riaghailtean an ionstans",
"Instance settings": "Roghainnean an ionstans",
"Instances": "Ionstansan",
"Instances following you": "Na h-ionstansan a tha a leantainn ort",
"Instances you follow": "Na h-ionstansan air a leanas tu",
"Integrate this event with 3rd-party tools and show metadata for the event.": "Amalaich an tachartas seo le innealan threas-phàrtaidhean is seall meata-dàta dhan tachartas.",
"Interact with a remote content": "Dèan conaltradh le susbaint chèin",
"Invite a new member": "Thoir cuireadh do bhall ùr",
"Invite member": "Thoir cuireadh do bhall",
"Invited": "Air cuireadh fhaighinn",
@ -352,6 +413,7 @@
"Italic": "Eadailteach",
"Join <b>{instance}</b>, a Mobilizon instance": "Faigh ballrachd air <b>{instance}</b>, seo ionstans Mobilizon",
"Join group": "Faigh ballrachd sa bhuidheann",
"Join group {group}": "Faigh ballrachd sa bhuidheann {group}",
"Keep the entire conversation about a specific topic together on a single page.": "Cùm an còmhradh gu lèir mu chuspair sònraichte còmhla air an aon duilleag.",
"Key words": "Faclan-luirg",
"Language": "Cànan",
@ -373,6 +435,7 @@
"License": "Ceadachas",
"Limited number of places": "Àiteachan cuingichte",
"List title": "Tiotal na liosta",
"Live": "Beò",
"Load more": "Luchdaich barrachd dheth",
"Load more activities": "Luchdaich barrachd gnìomhachdan",
"Loading comments…": "A luchdadh nam beachdan…",
@ -392,10 +455,12 @@
"Mark as resolved": "Cuir comharra gun deach fhuasgladh",
"Member": "Ball",
"Members": "Buill",
"Members-only post": "Post do bhuill a-mhàin",
"Mentions": "Iomraidhean",
"Message": "Teachdaireachd",
"Mobilizon": "Mobilizon",
"Mobilizon is a federated network. You can interact with this event from a different server.": "S e lìonra co-naisgte a th ann am Mobilizon S urrainn dhut eadar-ghnìomh a dhèanamh leis an tachartas seo o fhrithealaiche eile.",
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "S e bathar-bog co-naisgte a th ann am Mobilizon. Is ciall dha seo gur urrainn dhut a-rèir roghainnean rianachd a cho-nasgaidh eadar-ghnìomhan a ghabhail le susbaint o ionstansan eile, can gum faigh thu ballrachd ann am buidheann no gun gabh thu pàirt ann an tachartas a chaidh a chruthachadh am badeigin eile.",
"Mobilizon is a federated network. You can interact with this event from a different server.": "S e lìonra co-naisgte a th ann am Mobilizon S urrainn dhut conaltradh leis an tachartas seo o fhrithealaiche eile.",
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "S e bathar-bog co-naisgte a th ann am Mobilizon. Is ciall dha seo gur urrainn dhut a-rèir roghainnean rianachd a cho-nasgaidh conaltradh le susbaint o ionstansan eile, can gum faigh thu ballrachd ann am buidheann no gun gabh thu pàirt ann an tachartas a chaidh a chruthachadh am badeigin eile.",
"Mobilizon is a tool that helps you <b>find, create and organise events</b>.": "S e acainn a th ann am Mobilizon a chuidicheas thu ach <b>an lorg, an cruthaich s an cuir thu air dòigh tachartasan</b>.",
"Mobilizon is not a giant platform, but a <b>multitude of interconnected Mobilizon websites</b>.": "Chan e mòr-ùrlar a th ann am Mobilizon ach <b>pailteas de làraichean-lìn Mobilizon a tha co-cheangailte ri càch a chèile</b>.",
"Mobilizon software": "Bathar-bog Mobilizon",
@ -408,12 +473,13 @@
"Moderator": "Maor",
"Move": "Gluais",
"Move \"{resourceName}\"": "Gluais “{resourceName}”",
"Move resource to the root folder": "Gluais an goireas dhan phasgan freumha",
"Move resource to {folder}": "Gluais an goireas gu {folder}",
"My account": "An cunntas agam",
"My events": "Na tachartasan agam",
"My groups": "Na buidhnean agam",
"My identities": "Na dearbh-aithnean agam",
"NOTE! The default terms have not been checked over by a lawyer and thus are unlikely to provide full legal protection for all situations for an instance admin using them. They are also not specific to all countries and jurisdictions. If you are unsure, please check with a lawyer.": "AN AIRE! Tha dug neach-lagha sùil air na teirmichean tùsail idir agus mar sin chan eil sinn an dùil gun solar iad dìon laghail slàn anns gach suidheachadh do rianaire ionstans a chleachdas iad. Cuideachd, chan eil iad sònraichte do gach dùthaich is uachdranas laghail. Mur eil thu cinnteach, bruidhinn ri neach-lagha.",
"NOTE! The default terms have not been checked over by a lawyer and thus are unlikely to provide full legal protection for all situations for an instance admin using them. They are also not specific to all countries and jurisdictions. If you are unsure, please check with a lawyer.": "AN AIRE! Tha dug neach-lagha sùil air na teirmichean bunaiteach idir agus mar sin chan eil sinn an dùil gun solar iad dìon laghail slàn anns gach suidheachadh do rianaire ionstans a chleachdas iad. Cuideachd, chan eil iad sònraichte do gach dùthaich is uachdranas laghail. Mur eil thu cinnteach, bruidhinn ri neach-lagha.",
"Name": "Ainm",
"New discussion": "Deasbad ùr",
"New email": "Post-d ùr",
@ -422,6 +488,7 @@
"New members": "Buill ùra",
"New note": "Nòta ùr",
"New password": "Facal-faire ùr",
"New post": "Post ùr",
"New profile": "Pròifil ùr",
"Next": "Air adhart",
"Next month": "An ath mhìos",
@ -436,6 +503,7 @@
"No events found": "Cha deach tachartas sam bith a lorg",
"No follower matches the filters": "Chan eil neach-leantainn sam bith a maidseadh nan criathragan",
"No group found": "Cha deach buidheann sam bith a lorg",
"No group matches the filters": "Chan eil buidheann sam bith a maidseadh nan criathragan",
"No groups found": "Cha deach buidheann sam bith a lorg",
"No information": "Gun fhiosrachadh",
"No instance follows your instance yet.": "Chan eil ionstans sam bith a leantainn air an ionstans agad-sa fhathast.",
@ -444,13 +512,19 @@
"No instance to remove|Remove instance|Remove {number} instances": "Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstansan|Thoir air falbh {number} ionstans",
"No languages found": "Cha deach cànan a lorg",
"No member matches the filters": "Chan eil ball sam bith a maidseadh nan criathragan",
"No members found": "Cha deach ball a lorg",
"No memberships found": "Cha deach ballrachd a lorg",
"No message": "Chan eil teachdaireachd ann",
"No moderation logs yet": "Chan eil loga maorsainneachd ann fhathast",
"No more activity to display.": "Chan eil barrachd ghnìomhan ri an sealltainn ann.",
"No one is participating|One person participating|{going} people participating": "{count} chom-pàirtiche| {count} chom-pàirtiche| {count} com-pàirtichean| {count} com-pàirtiche",
"No open reports yet": "Chan eil gearan gun fhuasgladh ann",
"No organized events found": "Cha deach tachartas a tha ga chur air dòigh a lorg",
"No organized events listed": "Chan eil tachartas a tha ga chur air dòigh air an liosta",
"No participant matches the filters": "Chan eil com-pàirtiche sam bith a maidseadh nan criathragan",
"No participant to approve|Approve participant|Approve {number} participants": "Aontaich ri {number} chom-pàirtiche|Aontaich ri {number} chom-pàirtiche|Aontaich ri {number} com-pàirtichean|Aontaich ri {number} com-pàirtiche",
"No participant to reject|Reject participant|Reject {number} participants": "Diùlt {number} chom-pàirtiche|Diùlt {number} chom-pàirtiche|Diùlt {number} com-pàirtichean|Diùlt {number} com-pàirtiche",
"No participations listed": "Chan eil com-pàirtiche air an liosta",
"No posts found": "Cha deach post a lorg",
"No posts yet": "Chan eil post ann fhathast",
"No profile matches the filters": "Chan eil pròifil sam bith a maidseadh nan criathragan",
@ -461,15 +535,20 @@
"No resources selected": "Chaidh {count} ghoireas a thaghadh|Chaidh {count} ghoireas a thaghadh|Chaidh {count} goireasan a thaghadh|Chaidh {count} goireas a thaghadh",
"No resources yet": "Chan eil goireas ann fhathast",
"No results for \"{queryText}\"": "Cha deach toradh a lorg airson “{queryText}”",
"No results for {search}": "Cha deach toradh a lorg airson {search}",
"No rules defined yet.": "Cha deach riaghailt a mhìneachadh fhathast.",
"None": "Chan eil gin",
"Not accessible with a wheelchair": "Cha ghabh a ruigsinn le cathair-chuibhle",
"Not approved": "Gun aonta",
"Not confirmed": "Gun dearbhadh",
"Notes": "Nòtaichean",
"Nothing to see here": "Chan eil dad ri fhaicinn an-seo",
"Notification before the event": "Brath ron tachartas",
"Notification on the day of the event": "Brath air latha an tachartais",
"Notification settings": "Roghainnean nam brathan",
"Notifications": "Brathan",
"Notifications for manually approved participations to an event": "Brathan mu chom-pàirteachaichean air tachartas a chaidh aontachadh riutha à làimh",
"Notify participants": "Cuir brath dha na com-pàirtichean",
"Now, create your first profile:": "Nise, cruthaich a chiad phròifil agad:",
"Number of places": "Co mheud àite",
"OK": "Ceart ma-thà",
@ -479,11 +558,14 @@
"On {date} from {startTime} to {endTime}": "{date} o {startTime} gu {endTime}",
"On {date} starting at {startTime}": "{date}, a tòiseachadh aig {startTime}",
"On {instance}": "Air {instance}",
"On {instance} and other federated instances": "Air {instance} agus ionstansan co-naisgte eile",
"Online ticketing": "Ticeadan air loidhne",
"Only accessible through link": "Cha ghabh inntrigeadh ach le ceangal",
"Only accessible through link (private)": "Cha ghabh inntrigeadh ach le ceangal (prìobhaideach)",
"Only accessible to members of the group": "Cha ghabh inntrigeadh ach le buill a bhuidhinn",
"Only alphanumeric lowercased characters and underscores are supported.": "Chan eil taic ach ri litrichean gun sràcan, àireamhan is fo-loidhnichean.",
"Only group members can access discussions": "Chan fhaod ach buill a bhuidhinn na deasbadan inntrigeadh",
"Only group moderators can create, edit and delete events.": "Chan urrainn ach do mhaoir tachartasan a chruthachadh, a dheasachadh s a sguabadh às.",
"Only group moderators can create, edit and delete posts.": "Chan urrainn ach do mhaoir postaichean a chruthachadh, a dheasachadh s a sguabadh às.",
"Open": "Fosgailte",
"Open a topic on our forum": "Fosgail cuspair air a bhòrd-bhrath againn",
@ -492,17 +574,21 @@
"Or": "No",
"Organized": "Ga eagrachadh",
"Organized by": "Ga eagrachadh le",
"Organized by you": "Air a chur air dòigh leatsa",
"Organized by {name}": "Ga eagrachadh le {ainm}",
"Organizer": "Eagraiche",
"Organizer notifications": "Brathan an eagraiche",
"Organizers": "Eagraichean",
"Other": "Eile",
"Other actions": "Gnìomhan eile",
"Other notification options:": "Roghainnean eile nam brathan:",
"Other software may also support this.": "Dhfhaoidte gun doir bathar-bog eile taic ri seo cuideachd.",
"Otherwise this identity will just be removed from the group administrators.": "Air neo thèid an dearbh-aithne seo a thoirt air falbh le rianairean a bhuidhinn.",
"Page": "Duilleag",
"Page limited to my group (asks for auth)": "Duilleag cuingichte air a bhuidheann agam (thèid dearbhadh iarraidh)",
"Page not found": "Cha deach an duilleag a lorg",
"Parent folder": "Pasgan pàrant",
"Partially accessible with a wheelchair": "Gabhaidh pàirt dheth a ruigsinn le cathair-chuibhle",
"Participant": "Com-pàirtiche",
"Participants": "Com-pàirtichean",
"Participate": "Gabh pàirt ann",
@ -516,7 +602,10 @@
"Password (confirmation)": "Facal-faire (dearbhadh)",
"Password reset": "Ath-shuidheachadh an fhacail-fhaire",
"Past events": "Tachartasan a tha seachad mu thràth",
"PeerTube live": "PeerTube beò",
"PeerTube replay": "Ath-chluich PeerTube",
"Pending": "Ri dhèiligeadh",
"Personal feeds": "Inbhirean pearsanta",
"Pick": "Tagh",
"Pick a group": "Tagh buidheann",
"Pick a profile or a group": "Tagh pròifil no buidheann",
@ -538,10 +627,13 @@
"Preferences": "Roghainnean",
"Previous": "Air ais",
"Previous page": "An duilleag roimhpe",
"Price sheet": "Siota phrìsean",
"Privacy Policy": "Poileasaidh prìobhaideachd",
"Privacy policy": "Poileasaidh prìobhaideachd",
"Private event": "Tachartas prìobhaideach",
"Private feeds": "Inbhirean prìobhaideach",
"Profile": "Pròifil",
"Profile feeds": "Inbhirean na pròifile",
"Profiles": "Pròifilean",
"Profiles and federation": "Pròifilean agus co-nasgadh",
"Promote": "Àrdaich",
@ -552,16 +644,22 @@
"Public feeds": "Inbhirean poblach",
"Public iCal Feed": "Inbhir iCal poblach",
"Public page": "Duilleag phoblach",
"Public preview": "Ro-shealladh poblach",
"Publication date": "Ceann-là an fhoillseachaidh",
"Publish": "Foillsich",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Chaidh tachartasan fhoillseachadh le <b>{comments}</b> beachd(an) riutha agus <b>{participations}</b> com-pàirteachadh/com-pàirteachaidhean air an dearbhadh",
"Push": "Putadh",
"RSS/Atom Feed": "Inbhir RSS/Atom",
"Radius": "Astar",
"Recap every week": "Cuimhneachan seachdaineil",
"Receive one email for each activity": "Faigh post-d fa leth air gach gnìomhachd",
"Receive one email per request": "Faigh aon phost-d air gach iarrtas",
"Redirecting in progress…": "Gad ath-stiùireadh…",
"Redirecting to content…": "Gad ath-stiùireadh dhan t-susbaint…",
"Refresh profile": "Ath-nuadhaich a phròifil",
"Regenerate new links": "Ath-ghin ceanglaichean ùra",
"Region": "Roinn-dùthcha",
"Register": "Clàraich",
"Register an account on {instanceName}!": "Clàraich cunntas air {instanceName}!",
"Register on this instance": "Clàraich leis an ionstans seo",
"Registration is allowed, anyone can register.": "Tha clàradh ceadaichte, s urrainn do dhuine sam bith clàradh.",
@ -576,6 +674,7 @@
"Rename": "Thoir ainm ùr air",
"Rename resource": "Thoir ainm ùr air a ghoireas",
"Reopen": "Ath-fhosgail",
"Replay": "Ath-chluich",
"Reply": "Freagair",
"Report": "Dèan gearan",
"Report #{reportNumber}": "Gearan #{report_number}",
@ -604,6 +703,7 @@
"SSL/TLS": "SSL/TLS",
"Save": "Sàbhail",
"Save draft": "Sàbhail dreachd",
"Schedule": "Sgeideal",
"Search": "Lorg",
"Search events, groups, etc.": "Lorg tachartasan, buidhnean is msaa.",
"Searching…": "Ga lorg…",
@ -612,26 +712,34 @@
"Select a radius": "Tagh astar",
"Select a timezone": "Tagh roinn-tìde",
"Select languages": "Tagh na cànain",
"Select the activities for which you wish to receive an email or a push notification.": "Tagh na gnìomhachdan dhan fhaigh thu post-d no brath putaidh.",
"Send email": "Cuir post-d",
"Send notification e-mails": "Cuir puist-d bhrathan",
"Send the confirmation email again": "Cuir am post-d dearbhaidh a-rithist",
"Send the report": "Cuir an gearan",
"Set an URL to a page with your own privacy policy.": "Suidhich an t-URL air duilleag leis a phoileasaidh prìobhaideachd agad fhèin.",
"Set an URL to a page with your own terms.": "Suidhich an t-URL air duilleag leis na teirmichean agad fhèin.",
"Settings": "Roghainnean",
"Share": "Co-roinn",
"Share this event": "Co-roinn an tachartas seo",
"Share this group": "Co-roinn am buidheann seo",
"Short bio": "Sgeul-beatha goirid",
"Show map": "Seall am mapa",
"Show me where I am": "Seall càite a bheil mi",
"Show remaining number of places": "Seall na tha air fhàgail de dhàiteachan",
"Show the time when the event begins": "Seall an t-àm a thòisicheas an tachartas",
"Show the time when the event ends": "Seall an t-àm a chrìochnaicheas an tachartas",
"Sign Language": "Cainnt-shanais",
"Sign in with": "Clàraich a-steach le",
"Sign up": "Clàraich",
"Since you are a new member, private content can take a few minutes to appear.": "On a tha thu nad bhall ùr, dhfhaoidte gun doir e greiseag mus nochd susbaint phrìobhaideach.",
"Social": "Sòisealta",
"Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Tha cuid dhe na faclan a tha gan cleachdadh san teacsa gu h-ìosal, co-dhiù an e faclan teicnigeach a th annta gus nach e, mu bheachdan a tha caran doirbh a thuigsinn ma dhfhaoidte. Rinn sinn briathrachan ach am bhiod e na b fhasa dhut an tuigsinn:",
"Starts on…": "Àm-tòiseachaidh…",
"Status": "Staid",
"Street": "Sràid",
"Submit": "Cuir a-null",
"Subtitles": "Fo-thiotalan",
"Suspend": "Cuir à rèim",
"Suspend group": "Cuir am buidheann à rèim",
"Suspended": "Chaidh a chur à rèim",
@ -642,19 +750,27 @@
"Terms": "Teirmichean",
"Terms of service": "Teirmichean na seirbheise",
"Text": "Teacsa",
"The URL of a poll where the choice for the event date is happening": "URL cunntais-bheachd le roghainnean cheann-là dhan tachartas",
"The URL where the event can be watched live": "An t-URL far an urrainnear coimhead air an tachartas bheò",
"The URL where the event live can be watched again after it has ended": "An t-URL far an urrainnear coimhead air an tachartas bheò a-rithist às dèidh a thighinn gu crìoch",
"The account's email address was changed. Check your emails to verify it.": "Chaidh seòladh puist-d a chunntais atharrachadh. Thoir sùil air a phost-d agad airson a dhearbhadh.",
"The actual number of participants may differ, as this event is hosted on another instance.": "Dhfhaoidte gu bheil àireamh fhìrinneach nan com-pàirtichean diofraichte on a tha an tachartas seo ga òstadh air ionstans eile.",
"The content came from another server. Transfer an anonymous copy of the report?": "Thàinig an t-susbaint seo o fhrithealaiche eile. A bheil thu airson lethbhreac gun ainm dhen ghearan a thar-chur?",
"The draft event has been updated": "Chaidh dreachd an tachartais ùrachadh",
"The event has a sign language interpreter": "Bidh eadar-theangaiche cainnt-shanais aig an tachartas",
"The event has been created as a draft": "Chaidh an tachartas a chruthachadh mar dreachd",
"The event has been published": "Chaidh an tachartas fhoillseachadh",
"The event has been updated": "Chaidh an tachartas ùrachadh",
"The event has been updated and published": "Chaidh an tachartas ùrachadh s fhoillseachadh",
"The event hasn't got a sign language interpreter": "Cha bhi eadar-theangaiche cainnt-shanais aig an tachartas",
"The event live video contains subtitles": "Bidh fo-thiotalan aig an tachartas video bheò",
"The event live video does not contain subtitles": "Cha bhi fo-thiotalan aig an tachartas video bheò",
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "Chuir eagraiche an tachartais romhpa gun dearbh iad na com-pàirteachaidhean a làimh.Am bu mhiann leat nòta a chur ris a mhìnicheas carson a tha thu airson gabhail pàirt san tachartas seo?",
"The event organizer didn't add any description.": "Cha do chuir eagraiche an tachartais tuairisgeul sam bith ris.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "Dearbhaidh eagraiche an tachartais romhpa na com-pàirteachaidhean a làimh.On a chuir thu romhad pàirt a ghabhail ann gun chunntas, mìnich carson a bu mhiann leat pàirt a ghabhail san tachartas seo.",
"The event title will be ellipsed.": "Thèid tiotal an tachartais a ghiorrachadh.",
"The event will show as attributed to this group.": "Thèid an tachartas seo iomruineadh dhan bhuidheann seo.",
"The event will show as attributed to this profile.": "Thèid an tachartas seo iomruineadh dhan phròifil seo.",
"The event will show as attributed to your personal profile.": "Thèid an tachartas seo iomruineadh dhan phròifil phearsanta agad.",
"The event will show the group as organizer.": "Seallaidh an tachartas am buidheann mar eagraiche.",
"The event {event} was created by {profile}.": "Chaidh an tachartas {event} a chruthachadh le {profile}.",
@ -678,6 +794,7 @@
"The post {post} was deleted by {profile}.": "Chaidh am post {post} a sguabadh às le {profile}.",
"The post {post} was updated by {profile}.": "Chaidh am post {post} ùrachadh le {profile}.",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Thèid do ghearan a chuir dha na maoir aig an ionstans agad. S urrainn dhut mìneachadh carson a tha thu a dèanamh gearan mun t-susbaint seo gu h-ìosal.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "Tha an dealbh a thagh thu ro throm. Feumaidh tu faidhle a thaghadh a tha nas lugha na {size}.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Cuidichidh am fiosrachadh teicnigeach mun mhearachd gum fuasgail an luchd-leasachaidh an duilgheadas nas fhasa. Cuir ri do bheachd e.",
"The {default_privacy_policy} will be used. They will be translated in the user's language.": "Thèid {default_privacy_policy} a chleachdadh. Thèid a h-eadar-theangachadh gu cànan a chleachdaiche.",
"The {default_terms} will be used. They will be translated in the user's language.": "Thèid {default_terms} a chleachdadh. Thèid an eadar-theangachadh gu cànan a chleachdaiche.",
@ -686,20 +803,26 @@
"There will be no way to recover your data.": "Cha bhi dòigh sam bith ann gus an dàta agad aiseag.",
"There's no discussions yet": "Chan eil deasbad ann fhathast",
"These events may interest you": "Dhfhaoidte gu bheil ùidh agad sna tachartasan seo",
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Tha dàta nan tachartasan sa bheil gin dhe na pròifilean agad a gabhail pàirt no a chruthaich iad sna h-inbhirean seo. Bu chòir dhut an cumail prìobhaideach. Gheibh thu inbhirean do phròifilean sònraichte air duilleag deasachadh gach pròifile.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Tha dàta nan tachartasan sa bheil a phròifil shònraichte seo a gabhail pàirt no a chruthaich iad sna h-inbhirean seo. Bu chòir dhut an cumail prìobhaideach. Gheibh thu inbhirean dhan a h-uile pròifil agad ann an roghainnean nam brathan agad.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Gabhaidh an t-ionstans seo de Mhobilizon agus eagraiche an tachartais seo ri com-pàirteachadh gun ainm ach feumaidh tu a dhearbhadh air a phost-d.",
"This URL doesn't seem to be valid": "Chan eil coltas dligheach air an URL seo",
"This URL is not supported": "Chan eil taic ris an URL seo",
"This event has been cancelled.": "Chaidh an tachartas seo a chur gu neoini.",
"This event is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.",
"This group doesn't have a description yet.": "Chan eil tuairisgeul aig a bhuidheann seo fhathast.",
"This group is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.",
"This group is invite-only": "Feumaidh tu cuireadh airson ballrachd fhaighinn sa bhuidheann seo",
"This identifier is unique to your profile. It allows others to find you.": "Tha an t-aithnichear seo àraidh dhan phròifil agad. Leigidh e le càch do lorg.",
"This identity is not a member of any group.": "Chan eil an dearbh-aithne seo na ball ann am buidheann sam bith.",
"This information is saved only on your computer. Click for details": "Tha dèid am fiosrachadh seo a shàbhaladh ach air a choimpiutair agad. Briog airson mion-fhiosrachadh",
"This instance hasn't got push notifications enabled.": "Chan eil na brathan putaidh an comas aig an ionstans seo.",
"This instance isn't opened to registrations, but you can register on other instances.": "Chan eil an t-ionstans seo fosgailte a chùm clàraidh ach s urrainn dhut clàradh air ionstansan eile.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "S e an t-ionstans seo <b>{instanceName} ({domain})</b> a tha ag òstadh na pròifil agad, mar sin cuir ainm-san nad chuimhne.",
"This is a demonstration site to test Mobilizon.": "Seo làrach taisbeanaidh airson Mobilizon fheuchainn.",
"This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Tha seo coltach ris an ainm-chleachdaiche cho-naisgte agad (<code>{username}</code>) ach do bhuidhnean. Gabhaidh am buidheann a lorg leis sa cho-nasgadh agus s e ainm àraidh a bhios ann.",
"This month": "Am mìos seo",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Chan fhaigh ach na buill cothrom air a phost seo. Faodaidh tu inntrigeadh a chùm maorsainneachd a-mhàin on a tha thu nad mhaor air an ionstans seo.",
"This setting will be used to display the website and send you emails in the correct language.": "Thèid an roghainn seo a chleachdadh airson an làrach-lìn a shealltainn agus puist-d a chur thugad sa chànan cheart.",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Chan eil an làrach-lìn seo fo mhaorsainneachd agus thèid an dàta a chuireas tu a-steach a mhilleadh gu fèin-obrachail gach oidhche aig 00:01 (roinn-tìde Pharais).",
"This week": "An t-seachdain seo",
@ -717,15 +840,23 @@
"To register for an event by choosing one of your identities": "Airson clàradh le tachartas le tè dhe na dearbh-aithnean agad",
"Today": "An-diugh",
"Tomorrow": "A-màireach",
"Tools": "Innealan",
"Transfer to {outsideDomain}": "Tar-chur gu {outsideDomain}",
"Triggered profile refreshment": "Thèid a phròifil ath-nuadhachadh",
"Twitch live": "Twitch beò",
"Twitch replay": "Ath-chluiche Twitch",
"Twitter account": "Cunntas Twitter",
"Type": "Seòrsa",
"Type or select a date…": "Sgrìobh rudeigin no tagh ceann-latha…",
"URL": "URL",
"URL copied to clipboard": "Chaidh lethbhreac dhen URL a chur air an stòr-bhòrd",
"Unable to copy to clipboard": "Cha b urrainn dhuinn a chur air an stòr-bhòrd",
"Unable to create the group. One of the pictures may be too heavy.": "Cha b urrainn dhuinn am buidheann a chruthachadh. Dhfhaoidte gu bheil fear de na dealbhan ro throm.",
"Unable to create the profile. The avatar picture may be too heavy.": "Cha b urrainn dhuinn a phròifil a chruthachadh. Dhfhaoidte gu bheil dealbh an avatar ro throm.",
"Unable to detect timezone.": "Cha do dhaithnich sinn an roinn-tìde.",
"Unable to load event for participation. The error details are provided below:": "Cha b urrainn dhuinn an tachartas a luchdadh dhan chom-pàirteachadh. Chì thu fiosrachadh mun mhearachd gu h-ìosal:",
"Unable to save your participation in this browser.": "Cha b urrainn dhuinn do chom-pàirteachadh a shàbhaladh sa bhrabhsair seo.",
"Unable to update the profile. The avatar picture may be too heavy.": "Cha b urrainn dhuinn a phròifil ùrachadh. Dhfhaoidte gu bheil dealbh an avatar ro throm.",
"Unfortunately, this instance isn't opened to registrations": "Gu mì-fhortanach, chan eil an t-ionstans seo fosgailte a chùm clàraidh",
"Unfortunately, your participation request was rejected by the organizers.": "Gu mì-fhortanach, dhiùlt na h-eagraichean do chom-pàirteachadh.",
"Unknown": "Chan eil fhios",
@ -734,10 +865,12 @@
"Unknown value for the openness setting.": "Chaidh luach nach aithne dhuinn a shuidheachadh air dè cho fosgailte s a tha am buidheann.",
"Unsaved changes": "Atharraichean gun sàbhaladh",
"Unset group": "Dì-shuidhich am buidheann",
"Unsuspend": "Na cuir à rèim tuilleadh",
"Unsubscribe to browser push notifications": "Cuir crìoch air an fho-sgrìobhadh air brathan putaidh",
"Unsuspend": "Cuir an gnìomh a-rithist",
"Upcoming": "Ri thighinn",
"Upcoming events": "Tachartasan ri thighinn",
"Update": "Ùraich",
"Update app": "Ùraich an aplacaid",
"Update event {name}": "Ùraich an tachartas {name}",
"Update group": "Ùraich am buidheann",
"Update my event": "Ùraich an tachartas agam",
@ -746,14 +879,18 @@
"Uploaded media size": "Meud a mheadhain a chaidh a luchdadh suas",
"Use my location": "Cleachd an t-ionad agam",
"User": "Cleachdaiche",
"User settings": "Roghainnean a chleachdaiche",
"Username": "Ainm-cleachdaiche",
"Users": "Cleachdaichean",
"View a reply": "Seall {totalReplies} fhreagairt|Seall {totalReplies} fhreagairt|Seall {totalReplies} freagairtean|Seall {totalReplies} freagairt",
"View account on {hostname} (in a new window)": "Seall an cunntas air {hostname} (ann an uinneag ùr)",
"View all": "Seall a h-uile",
"View all events": "Seall a h-uile tachartas",
"View all posts": "Seall a h-uile post",
"View event page": "Seall duilleag an tachartais",
"View everything": "Seall a h-uile càil",
"View less": "Seall nas lugha",
"View more": "Seall barrachd",
"View page on {hostname} (in a new window)": "Seall an duilleag air {hostname} (ann an uinneag ùr)",
"Visibility was set to an unknown value.": "Chaidh luach nach aithne dhuinn a shuidheachadh air an t-so-fhaicsinneachd.",
"Visibility was set to private.": "Chaidh so-fhaicsinneachd phrìobhaideach a shuidheachadh air.",
@ -762,20 +899,26 @@
"Visible everywhere on the web (public)": "Chithear air feadh an lìn e (poblach)",
"Waiting for organization team approval.": "A feitheamh air aontachadh leis an sgioba eagrachaidh.",
"Warning": "Rabhadh",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Cha b urrainn dhuinn an com-pàirteachadh agad a shàbhaladh sa bhrabhsair seo. Na gabh dragh, dhearbh thu gun gabh thu pàirt ann ach cha b urrainn dhuinn sin a shàbhaladh sa bhrabhsair seo ri linn duilgheadas teicnigeach.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Bheir sinn piseach air a bhathar-bhog le taic do bheachdan. Tha dà dhòigh ann airson innse dhuinn mu dhèidhinn na trioblaide seo (gu mì-fhortanach, feumaidh tu cunntas a chruthachadh dhaibh):",
"We just sent an email to {email}": "Tha sinn air post-d a chur gu {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Cleachdaidh sinn an roinn-tìde agad ach an cuir sinn na brathan mu thachartas thugad aig an àm cheart.",
"We will redirect you to your instance in order to interact with this event": "Bheir sinn dhan ionstans agad-sa thu airson eadar-ghnìomh a ghabhail leis an tachartas seo",
"We will redirect you to your instance in order to interact with this group": "Bheir sinn dhan ionstans agad-sa thu airson eadar-ghnìomh a ghabhail leis a bhuidheann seo",
"We will redirect you to your instance in order to interact with this event": "Bheir sinn dhan ionstans agad-sa thu airson conaltradh leis an tachartas seo",
"We will redirect you to your instance in order to interact with this group": "Bheir sinn dhan ionstans agad-sa thu airson conaltradh leis a bhuidheann seo",
"We'll send you an email one hour before the event begins, to be sure you won't forget about it.": "Cuiridh sinn post-d thugad uair a thìde mus tòisich an tachartas a dhèanamh cinnteach nach dìochuimhnich thu e.",
"We'll use your timezone settings to send a recap of the morning of the event.": "Cleachdaidh sinn an roinn-tìde agad airson cuimhneachan a chur thugad sa mhadainn ron tachartas.",
"Website": "Làrach-lìn",
"Website / URL": "Làrach-lìn / URL",
"Weekly email summary": "Geàrr-chunntas puist-d seachdaineil",
"Welcome back {username}!": "Fàilte air ais, {username}!",
"Welcome back!": "Fàilte air ais!",
"Welcome to Mobilizon, {username}!": "Fàilte gu Mobilizon, {username}!",
"What can I do to help?": "Dè nì mi airson cuideachadh?",
"Wheelchair accessibility": "Inntrigeadh cathrach-cuibhle",
"When a moderator from the group creates an event and attributes it to the group, it will show up here.": "Nuair a chruthaicheas maor a bhuidhinn tachartas le iomruineadh dhan bhuidheann, nochdaidh e an-seo.",
"Whether the event is accessible with a wheelchair": "Co-dhiù am faighear dhan tachartas le cathair-chuibhle gus nach fhaigh",
"Whether the event is interpreted in sign language": "Co-dhiù am faighear eadar-theangachadh gu cainnt-shanais aig an tachartas gus nach fhaigh",
"Whether the event live video is subtitled": "Co-dhiù am bi fo-thiotalan aig an tachartas gus nach bi",
"Who can view this event and participate": "Cò chì an tachartas seo s a dhfhaodas pàirt a ghabhail ann",
"Who can view this post": "Cò chì am post seo",
"Who published {number} events": "A dhfhoillsich {number} tachartas",
@ -789,12 +932,15 @@
"You archived the discussion {discussion}.": "Chuir thu an deasbad {discussion} san tasg-lann.",
"You are not an administrator for this group.": "Chan eil thu nad rianaire sa bhuidheann seo.",
"You are not part of any group.": "Chan eil thu nad bhall ann am buidheann sam bith.",
"You are offline": "Tha thu far loidhne",
"You are participating in this event anonymously": "Tha thu a gabhail pàirt san tachartas seo gun ainm",
"You are participating in this event anonymously but didn't confirm participation": "Tha thu a gabhail pàirt san tachartas seo gun ainm ach cha do dhearbh thu do chom-pàirteachadh",
"You can add tags by hitting the Enter key or by adding a comma": "S urrainn dhut tagaichean a chur ris s tu a brùthadh air Enter no a cur cromag ris",
"You can pick your timezone into your preferences.": "S urrainn dhut an roinn-tìde a thaghadh sna roghainnean agad.",
"You can try another search term or drag and drop the marker on the map": "S urrainn dhut facal-luirg eile fheuchainn no an comharra a shlaodadh agus leigeil às air a mhapa",
"You can't change your password because you are registered through {provider}.": "Chan urrainn dhut am facal-faire agad atharrachadh air sgàth s gun do clàraich thu le {provider}.",
"You can't use push notifications in this browser.": "Chan urrainn dhut brathan putaidh a chleachdadh sa bhrabhsair seo.",
"You changed your email or password": "Dhatharraich thu am post-d no am facal-faire agad",
"You created the discussion {discussion}.": "Chruthaich thu an deasbad {discussion}.",
"You created the event {event}.": "Chruthaich thu an tachartas {event}.",
"You created the folder {resource}.": "Chruthaich thu am pasgan {resource}.",
@ -822,6 +968,7 @@
"You invited {member}.": "Thug thu cuireadh dha {member}.",
"You may clear all participation information for this device with the buttons below.": "S urrainn dhut gach fiosrachadh mun chom-pàirteachadh a shuathadh bàn on uidheam seo leis na putanan gu h-ìosal.",
"You may now close this window, or {return_to_event}.": "S urrainn dhut an uinneag seo a dhùnadh a-nis no {return_to_event}.",
"You may show some members as contacts.": "Faodaidh tu cuid a bhuill a shealltainn nan luchd-aithne.",
"You moved the folder {resource} into {new_path}.": "Ghluais thu am pasgan {resource} gu {new_path}.",
"You moved the folder {resource} to the root folder.": "Ghluais thu am pasgan {resource} dhan phasgan freumhach.",
"You moved the resource {resource} into {new_path}.": "Ghluais thu an goireas {resource} gu {new_path}.",
@ -853,8 +1000,11 @@
"You will find here all the events you have created or of which you are a participant.": "Chì thu a h-uile tachartas a chruthaich thu no sa bheil thu a gabhail pàirt an-seo.",
"You wish to participate to the following event": "Tha thu airson pàirt a ghabhail san tachartas a leanas",
"You'll get a weekly recap every Monday for upcoming events, if you have any.": "Gheibh thu cuimhneachan gach madainn DiLuain mu na tachartasan ri thighinn ma tha gin agad.",
"You'll need to change the URLs where there were previously entered.": "Feumaidh tu na h-URLaichean atharrachadh far an deach an cur a-steach roimhe.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Feumaidh tu URL a bhuidhinn a thar-chur ach an urrainn do dhaoine pròifil a bhuidhinn inntrigeadh. Cha ghabh am buidheann a lorg le gleus nan lorg aig Mobilizon no le einnseanan-luirg àbhaisteach.",
"You'll receive a confirmation email.": "Gheibh thu post-d dearbhaidh.",
"YouTube live": "YouTube beò",
"YouTube replay": "Ath-chluich YouTube",
"Your account has been successfully deleted": "Chaidh an cunntas agad a sguabadh às",
"Your account has been validated": "Chaidh an cunntas agad a dhearbhadh",
"Your account is being validated": "Tha an cunntas agad ga dhearbhadh",
@ -882,6 +1032,8 @@
"Your timezone was detected as {timezone}.": "Mhothaich sinn dha {timezone} mar an roinn-tìde agad.",
"Your timezone {timezone} isn't supported.": "Chan eil taic ris an roinn-tìde {timezone} agad.",
"Your upcoming events": "Na tachartasan a tha gu bhith agad",
"Zoom in": "Sùm a-steach",
"Zoom out": "Sùm a-mach",
"[This comment has been deleted by it's author]": "[Chaidh am beachd seo a sguabadh às leis an ùghdar]",
"[This comment has been deleted]": "[Chaidh am beachd seo a sguabadh às]",
"[deleted]": "[air a sguabadh às]",
@ -892,9 +1044,11 @@
"contact uninformed": "gun fhiosrachadh conaltraidh",
"create a group": "buidheann a chruthachadh",
"create an event": "tachartas a chruthachadh",
"default Mobilizon privacy policy": "poileasaidh Mhobilizon thùsail",
"default Mobilizon terms": "teirmichean Mhobilizon tùsail",
"default Mobilizon privacy policy": "poileasaidh Mhobilizon bhunaiteach",
"default Mobilizon terms": "teirmichean Mhobilizon bunaiteach",
"e.g. 10 Rue Jangot": "m.e. 10 Rathad a Chidhe",
"e.g. Accessibility, Twitch, PeerTube": "m.e. So-ruigsinneachd, Twitch, PeerTube",
"enable the feature": "an gleus a chur an comas",
"explore the events": "rùrachadh sna tachartasan",
"explore the groups": "rùrachadh sna buidhnean",
"full rules": "riaghailtean slàna",
@ -909,10 +1063,13 @@
"{approved} / {total} seats": "{approved} / {total} àite(achan)",
"{available}/{capacity} available places": "{available}/{capacity} àite air fhàgail|{available}/{capacity} àite air fhàgail|{available}/{capacity} àiteachan air fhàgail|{available}/{capacity} àite air fhàgail",
"{count} km": "{count} km",
"{count} members": "{count} bhall|{count} bhall|{count} buill|{count} ball",
"{count} participants": "{count} chom-pàirtiche| {count} chom-pàirtiche| {count} com-pàirtichean| {count} com-pàirtiche",
"{count} requests waiting": "Tha {count} iarrtas(an) a feitheamh",
"{count} team members": "Buill an sgioba ({count})",
"{folder} - Resources": "{folder} Goireasan",
"{group} activity timeline": "Loidhne-ama nan gnìomhachdan aig {group}",
"{group} events": "Tachartasan {group}",
"{group}'s events": "Na tachartasan aig {group}",
"{instanceName} is an instance of the {mobilizon} software.": "Tha {instanceName} na ionstans dhen bhathar-bhog {mobilizon}.",
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "Tha {instanceName} na ionstans dhe {mobilizon_link}, bathar-bog saor a tha ga thogail leis a choimhearsnachd.",
@ -923,18 +1080,24 @@
"{moderator} added a note on {report}": "Chuir {moderator} nòta ri {report}",
"{moderator} closed {report}": "Dhùin {moderator} {report}",
"{moderator} deleted an event named \"{title}\"": "Sguab {moderator} às tachartas air a bheil “{title}”",
"{moderator} has deleted a comment from {author}": "Sguab {moderator} beachd le {author} às",
"{moderator} has deleted a comment from {author} under the event {event}": "Sguab {moderator} beachd le {author} às fon tachartas {event}",
"{moderator} has deleted user {user}": "Sguab {moderator} às an cleachdaiche {user}",
"{moderator} has unsuspended profile {profile}": "Ghnìomhaich {moderator} a phròifil {profile}",
"{moderator} has done an unknown action": "Ghabh {moderator} gnìomh nach aithne dhuinn",
"{moderator} has unsuspended group {profile}": "Chuir {moderator} buidheann {profile} an gnìomh",
"{moderator} has unsuspended profile {profile}": "Chuir {moderator} pròifil {profile} an gnìomh",
"{moderator} marked {report} as resolved": "Chuir {moderator} comharra gun deach {report} fhuasgladh",
"{moderator} reopened {report}": "Dhfhosgail {moderator} {report} a-rithist",
"{moderator} suspended group {profile}": "Chuir {moderator} buidheann {profile} à rèim",
"{moderator} suspended profile {profile}": "Chuir {moderator} a phròifil {profile} à rèim",
"{nb} km": "{nb} km",
"{number} members": "{number} ball/buill",
"{number} memberships": "Ballrachdan ({number})",
"{number} organized events": "Tha {number} tachartas ga chur air dòigh|Tha {number} thachartas ga chur air dòigh|Tha {number} tachartasan gan cur air dòigh|Tha {number} tachartas gan cur air dòigh",
"{number} participations": "{number} chom-pàirtiche|{number} chom-pàirtiche|{number} com-pàirtichean|{number} com-pàirtiche",
"{number} posts": "{number} phost|{number} phost|{number} postaichean|{number} post",
"{old_group_name} was renamed to {group}.": "Chaidh {group} a thoirt air {old_group_name}.",
"{profile} (by default)": "{profile} (o thùs)",
"{profile} (by default)": "{profile} (a ghnàth)",
"{profile} added the member {member}.": "Chuir {profile} am ball {member} ris.",
"{profile} archived the discussion {discussion}.": "Chuir {profile} an deasbad {discussion} san tasg-lann.",
"{profile} created the discussion {discussion}.": "Chruthaich {profile} an deasbad {discussion}.",

View File

@ -4,6 +4,7 @@
"(this folder)": "(este cartafol)",
"(this link)": "(esta ligazón)",
"+ Add a resource": "+ Engadir un recurso",
"+ Create a post": "+ Crear publicación",
"+ Create an event": "+ Crear un evento",
"+ Post a public message": "+ Publicar unha mensaxe pública",
"+ Start a discussion": "+ Comezar un debate",
@ -47,6 +48,7 @@
"Account": "Conta",
"Actions": "Accións",
"Activate browser notification": "Activar notificacións no navegador",
"Activate browser push notifications": "Activar notificacións push do navegador",
"Activated": "Activado",
"Active": "Activa",
"Activity": "Actividade",
@ -60,6 +62,7 @@
"Add a todo": "Engadir tarefas pendentes",
"Add an address": "Engadir un enderezo",
"Add an instance": "Engadir unha instancia",
"Add new…": "Engadir novo…",
"Add some tags": "Engadir etiquetas",
"Add to my calendar": "Engadir ó meu calendario",
"Additional comments": "Comentarios adicionais",
@ -88,6 +91,7 @@
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Unha instancia é unha versión do software Mobilizon instalada nun servidor. Calquera persoa pode instalar unha instancia usando o {mobilizon_software} ou outras apps federadas, coñecidas como \"fediverso\". O nome desta instancia é {instance_name}. Mobilizon é unha rede federada de múltiples instancias (como os servidores de email), usuarias rexistradas en diferentes servidores que poden comunicarse incluso se non están rexistradas na mesma instancia.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Unha \"interface para a programación de aplicacións\" ou \"API\" é un protocolo de comunicacións que permite a compoñentes de software comunicarse entre eles. A API Mobilizon, por exemplo, permite que software de terceiros se comunique con instancias Mobilizon para realizar certas accións, como publicar eventos no teu nome, de xeito automático e remoto.",
"And {number} comments": "E {number} comentarios",
"Announcements and mentions notifications are always sent straight away.": "As notificacións de mencións e anuncios sempre son enviadas de todas formas.",
"Anonymous participant": "Participante anónimo",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Ós participantes anónimos pediráselle que confirmen a súa participación a través de email.",
"Anonymous participations": "Participacións anónimas",
@ -108,6 +112,7 @@
"Are you sure you want to delete this entire discussion?": "Tes a certeza de querer eliminar o debate completo?",
"Are you sure you want to delete this event? This action cannot be reverted.": "¿Tes a certeza de que queres eliminar este evento? Esta acción non é reversible.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Como a organización do evento escolleu validar manualmente as solicitudes, a túa participación estará realmente confirmada cando recibas un email informándote.",
"Ask your instance admin to {enable_feature}.": "Pídelle á administración da instancia que {enable_feature}.",
"Assigned to": "Asignado a",
"Atom feed for events and posts": "Fonte Atom para eventos e publicacións",
"Avatar": "Avatar",
@ -116,6 +121,7 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Antes de poder conectarte, tes que premer na ligazón incluída para validar a túa conta.",
"Begins on": "Comeza o",
"Bold": "Resaltado",
"Breadcrumbs": "Breadcrumbs",
"Browser notifications": "Notificacións do navegador",
"By @{group}": "Por @{group}",
"By @{username}": "Por @{username}",
@ -166,6 +172,7 @@
"Contact": "Contactar",
"Continue editing": "Continuar editando",
"Cookies and Local storage": "Cookies e Almacenaxe Local",
"Copy URL to clipboard": "Copiar URL ao portapapeis",
"Copy details to clipboard": "Copiar detalles ao portapapeis",
"Country": "País",
"Create": "Crear",
@ -254,6 +261,7 @@
"Edit post": "Editar publicación",
"Edit profile {profile}": "Editar perfil {profile}",
"Edited {ago}": "Editado {ago}",
"Edited {relative_time} ago": "Editado hai {relative_time}",
"Eg: Stockholm, Dance, Chess…": "Ex: Silleda, Baile, Billarda…",
"Either on the {instance} instance or on another instance.": "Ben na instancia {instance} ou en calquera outra instancia.",
"Either the account is already validated, either the validation token is incorrect.": "Ou a conta xa está validada, ou o token de validación non é correcto.",
@ -280,12 +288,15 @@
"Error while login with {provider}. This login provider doesn't exist.": "Erro ó conectar con {provider}. Este provedor podería non existir.",
"Error while reporting group {groupTitle}": "Erro ó denunciar o grupo {groupTitle}",
"Error while saving report.": "Fallo ó gardar a denuncia.",
"Error while subscribing to push notifications": "Fallou a subscrición ás notificacións push",
"Error while suspending group": "Erro ao suspender o grupo",
"Error while updating participation status inside this browser": "Erro ao actualizar o estado de participación desde este navegador",
"Error while validating account": "Fallo ó validar a conta",
"Error while validating participation": "Fallo ó validar a participación",
"Error while validating participation request": "Erro ó validar a solicitude de participación",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "Alternativa ética a Facebook events, grupos e páxinas, Mobilizon é unha <b>ferramenta deseñada para servirte</b>. E punto.",
"Event": "Evento",
"Event URL": "URL do evento",
"Event already passed": "O evento xa rematou",
"Event cancelled": "Evento cancelado",
"Event creation": "Evento creado",
@ -314,6 +325,7 @@
"Find an address": "Atopar un enderezo",
"Find an instance": "Atopar unha instancia",
"Find another instance": "Atopa outra instancia",
"Find or add an element": "Atopa ou engade un elemento",
"Follower": "Seguidora",
"Followers": "Seguidoras",
"Followers will receive new public events and posts.": "As seguidoras recibirán os novos eventos públicos e publicacións.",
@ -341,6 +353,7 @@
"Group Followers": "Agrupar seguidoras",
"Group List": "Lista do grupo",
"Group Members": "Membros do grupo",
"Group URL": "URL do grupo",
"Group activity": "Actividade do grupo",
"Group address": "Enderezo do grupo",
"Group display name": "Nome mostrado do grupo",
@ -462,6 +475,7 @@
"Mark as resolved": "Marcar como resolto",
"Member": "Membro",
"Members": "Membros",
"Members-only post": "Publicación só para membros",
"Mentions": "Mencións",
"Message": "Mensaxe",
"Mobilizon": "Mobilizon",
@ -545,6 +559,7 @@
"No resources selected": "Sen recursos seleccionados|Un recurso seleccionado|{count} recursos seleccionados",
"No resources yet": "Aínda non hai recursos",
"No results for \"{queryText}\"": "Sen resultados para \"{queryText}\"",
"No results for {search}": "Sen resultados para {search}",
"No rules defined yet.": "Sen normas definidas.",
"None": "Nada",
"Not approved": "Non aprobado",
@ -573,6 +588,7 @@
"Only alphanumeric characters and underscores are supported.": "Só se permiten caracteres alfanuméricos e trazo baixo.",
"Only alphanumeric lowercased characters and underscores are supported.": "Só se permiten caracteres alfanuméricos en minúsculas e trazo baixo.",
"Only group members can access discussions": "Só os membros do grupo poden acceder ás conversas",
"Only group moderators can create, edit and delete events.": "Só os moderadores do grupo poden crear, editar e eliminar eventos.",
"Only group moderators can create, edit and delete posts.": "Só as moderadoras do grupo poden crear, editar e eliminar publicacións.",
"Open": "Abrir",
"Open a topic on our forum": "Abrir un tema no noso foro",
@ -588,6 +604,7 @@
"Organizer notifications": "Notificacións da organización",
"Organizers": "Organizado por",
"Other": "Outro",
"Other actions": "Outras accións",
"Other notification options:": "Outras opcións de notificación:",
"Other software may also support this.": "Outro software tamén podería soportar esto.",
"Otherwise this identity will just be removed from the group administrators.": "Se non esta identidade será eliminada do grupo de administradoras.",
@ -658,6 +675,7 @@
"Radius": "Radio",
"Read Framasofts statement of intent on the Framablog": "Ler no Framablog a declaración de Framasoft sobre as súas intencións",
"Recap every week": "Resumen semanal",
"Receive one email for each activity": "Recibir un email por cada actividade",
"Receive one email per request": "Recibir un email por solicitude",
"Redirecting to content…": "Redirixindo ó contido…",
"Redirecting to event…": "Redirixindo ó evento…",
@ -719,6 +737,7 @@
"Select languages": "Escolle idiomas",
"Select the activities for which you wish to receive an email or a push notification.": "Escolle as actividades das que queres recibir un email ou notificación push.",
"Send email": "Enviar email",
"Send notification e-mails": "Enviar emails de notificacións",
"Send the confirmation email again": "Enviar o email de confirmación outra vez",
"Send the report": "Enviar a denuncia",
"Set an URL to a page with your own privacy policy.": "Establece o URL da páxina coa túa política de privacidade.",
@ -811,6 +830,7 @@
"This identity is not a member of any group.": "Esta identidade non é membro de ningún grupo.",
"This information is saved only on your computer. Click for details": "Esta información gárdase só na túa computadora. Preme para saber máis",
"This installation (called “an instance“) can easily {interconnect}, thanks to {protocol}.": "Esta instalación (chamada \"unha instancia\") pode conectarse {interconnect}, grazas a {protocol}.",
"This instance hasn't got push notifications enabled.": "Esta instancia non ten as notificacións push activadas.",
"This instance isn't opened to registrations, but you can register on other instances.": "Esta instancia non ten o rexistro aberto, mais podes rexistrarte noutras instancias.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Nesta instancia, <b>{instanceName} ({domain})</b>, está o teu perfil, así que lembra o seu nome.",
"This is a demonstration site to test Mobilizon.": "Esta é unha web de exemplo para probar Mobilizon.",
@ -818,6 +838,7 @@
"This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Esto é como o teu nome de usuaria federado (<code>{username}</code> pero para grupos. Permite que o grupo sexa atopado na federación, e garántese que sexa único.",
"This is like your federated username (<code>{username}</code>) for groups. It will allow you to be found on the federation, and is guaranteed to be unique.": "Esto é como o teu nome de usuaria (<code>{username}</code>) para grupos. Permitirá que te atopen na federación e garántese que é único.",
"This month": "Este mes",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Esta publicación só é accesible para membros. Ti tes acceso a ela para poder moderala xa que es moderadora da instancia.",
"This setting will be used to display the website and send you emails in the correct language.": "Este axuste usarase para mostrar o sitio web e enviarche os emails no idioma correcto.",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Este sitio web non está moderado e os datos que introduzas serán eliminados cada día ás 00:01 (hora de París).",
"This week": "Esta semana",
@ -859,6 +880,7 @@
"Unset group": "Sacar do grupo",
"Unsubscribe to WebPush": "Quitar subscrición a WebPush",
"Unsubscribe to browser notifications": "Non permitir notificacións no navegador",
"Unsubscribe to browser push notifications": "Retirar subscrición ás notificacións push do navegdor",
"Unsuspend": "Reactivar",
"Upcoming": "Próximamente",
"Upcoming events": "Eventos próximos",
@ -882,6 +904,8 @@
"View all upcoming events": "Ver tódolos próximos eventos",
"View event page": "Ver páxina do evento",
"View everything": "Velo todo",
"View less": "Ver menos",
"View more": "Ver máis",
"View page on {hostname} (in a new window)": "Ver páxina en {hostname} (nova ventá)",
"Visibility was set to an unknown value.": "Estableceuse a visibilidade a un valor descoñecido.",
"Visibility was set to private.": "Estableceuse a visibilidade como privada.",
@ -892,6 +916,7 @@
"Warning": "Aviso",
"We asked professional designers to help us develop our vision for Mobilizon. We took time to study the {digital_habits} in order to understand the features they need to gather, organize, and mobilize so that right from its conception, Mobilizon would {fit_needs_uses_people} who are going to use it.": "Pedímoslle a deseñadoras profesionais que nos axuden a desenvolver as nosas ideas para Mobilizon. Levounos tempo estudar os {digital_habits} para poder comprender as características que se precisan para xuntar, organizar e mobilizar e que desde os seus comezos Mobilizon {fit_needs_uses_people} das persoas que a usen.",
"We cant change the world from within Facebook. The tool we dream of, surveillance capitalism corporations wont develop, as they cannot profit from it. This is an opportunity to build something better, by taking another approach.": "Non podemos cambiar o mundo desde dentro de Facebook. A ferramenta que precisamos non nola vai dar o capitalismo de vixilancia, porque non pode sacar cartos dela. Esta é unha oportunidade para construír algo mellor, adoptando un enfoque diferente.",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Non puidemos gardar a túa participación neste navegador. Non te preocupes, confirmaches correctamente a túa participación, só que non puidemos gardar o estado neste navegador por algunha cuestión técnica.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Melloramos este software grazas á túa colaboración. Infórmanos acerca deste asunto, tes dúas posibilidades (desafortunadamente as dúas requiren a creación dunha conta):",
"We just sent an email to {email}": "Enviámosche un email a {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Usamos a túa zona horaria para asegurarnos de que recibes as notificacións para o evento na hora correcta.",
@ -902,6 +927,7 @@
"We'll use your timezone settings to send a recap of the morning of the event.": "Usaremos os axustes de zona horaria para enviar un recordatorio na mañán do evento.",
"Website": "Sitio web",
"Website / URL": "Sitio web / URL",
"Weekly email summary": "Email co resumo semanal",
"Welcome back {username}!": "Benvida {username}!",
"Welcome back!": "Benvida!",
"Welcome to Mobilizon, {username}!": "Benvida a Mobilizon, {username}!",
@ -932,6 +958,7 @@
"You can try another search term or drag and drop the marker on the map": "Podes intentalo con outro termo de busca ou arrastrar e soltar a marca no mapa",
"You can't change your password because you are registered through {provider}.": "Non podes cambiar o contrasinal porque estás rexistrada en {provider}.",
"You can't use notifications in this browser.": "Non podes usar notificacións neste navegador.",
"You can't use push notifications in this browser.": "Non podes usar notificacións push neste navegador.",
"You can't use webpush in this browser.": "Non podes usar webpush neste navegador.",
"You changed your email or password": "Cambiaches ou email ou contrasinal",
"You created the discussion {discussion}.": "Creaches o debate {discussion}.",
@ -1042,6 +1069,8 @@
"default Mobilizon terms": "termos por omisión de Mobilizon",
"digital habits of activists": "costumes dixitais das activistas",
"e.g. 10 Rue Jangot": "ex. Rúa do Can 7",
"e.g. Accessibility, Twitch, PeerTube": "ex. Accessibility, Twitch, PeerTube",
"enable the feature": "activar a característica",
"explore the events": "explorar os eventos",
"explore the groups": "explorar grupos",
"firstDayOfWeek": "0",

702
js/src/i18n/id.json Normal file
View File

@ -0,0 +1,702 @@
{
"#{tag}": "#{tag}",
"(this folder)": "(folder ini)",
"(this link)": "(tautan ini)",
"+ Create a post": "+ Buat sebuah postingan",
"+ Create an event": "+ Buat sebuah acara",
"@{group}": "@{group}",
"@{username}": "@{username}",
"@{username} ({role})": "@{username} ({role})",
"A link to a page presenting the event schedule": "Tautan ke halaman yang menunjukkan jadwal acara",
"A member has been updated": "Seorang anggota telah diperbarui",
"A member requested to join one of my groups": "Seorang anggota ingin bergabung ke salah satu kelompok saya",
"A new version is available.": "Versi baru tersedia.",
"A post has been published": "Sebuah postingan telah dipublikasikan",
"A post has been updated": "Sebuah postingan telah diperbarui",
"A practical tool": "Alat yang praktis",
"A validation email was sent to {email}": "Surel validasi telah dikirim ke {email}",
"API": "API",
"About": "Tentang",
"About Mobilizon": "Tentang Mobilizon",
"About anonymous participation": "Tentang keikutsertaan anonim",
"About this event": "Tentang acara ini",
"About this instance": "Tentang instansi ini",
"About {instance}": "Tentang {instance}",
"Accept": "Terima",
"Accessibility": "Aksesibilitas",
"Account": "Akun",
"Activate browser push notifications": "Aktifkan notifikasi push browser",
"Activated": "Diaktifkan",
"Active": "Aktif",
"Activity": "Aktivitas",
"Add": "Tambahkan",
"Add / Remove…": "Tambah / Hapus…",
"Add a group": "Tambahkan kelompok",
"Add a new post": "Tambahkan postingan baru",
"Add a note": "Tambahkan catatan",
"Add an address": "Tambahkan alamat",
"Add an instance": "Tambahkan instansi",
"Add new…": "Tambahkan…",
"Add some tags": "Tambahkan beberapa tag",
"Add to my calendar": "Tambahkan ke kalender saya",
"Additional comments": "Komentar tambahan",
"Admin": "Admin",
"Admin settings successfully saved.": "Pengaturan admin berhasil disimpan.",
"Administration": "Administrasi",
"Administrator": "Administrator",
"All activities": "Semua aktivitas",
"All good, let's continue!": "Semuanya bagus, ayo lanjut!",
"All the places have already been taken": "Semua tempat telah diambil",
"Allow all comments from users with accounts": "Izinkan komentar dari pengguna yang telah masuk",
"Allow registrations": "Izinkan pendaftaran",
"An error has occured while refreshing the page.": "Sebuah kesalahan terjadi ketika memuat ulang halaman.",
"An error has occured. Sorry about that. You may try to reload the page.": "Terjadi suatu kesalahan. Maaf. Anda dapat mencoba memuat ulang halaman.",
"An ethical alternative": "Alternatif yang etis",
"An event I'm going to has been updated": "Sebuah acara yang saya ikuti telah diperbarui",
"An event I'm going to has posted an announcement": "Sebuah acara yang saya ikuti memposting sebuah pengumuman",
"An event I'm organizing has a new comment": "Sebuah acara yang saya selenggarakan memiliki komentar baru",
"And {number} comments": "Dan {number} komentar",
"Anonymous participant": "Peserta anonim",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Peserta-peserta anonim akan diminta untuk mengkonfirmasi keikutsertaannya melalui surel.",
"Anonymous participations": "Peserta-peserta anonim",
"Any day": "Kapanpun",
"Anyone can join freely": "Siapapun dapat bergabung",
"Application": "Aplikasi",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Apakah Anda yakin ingin menghapus seluruh akun Anda? Anda akan kehilangan semuanya. Identitas, pengaturan, acara yang dibuat, pesan dan keikutsertaan akan hilang selamanya.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Apakah Anda yakin ingin <b>menghapus</b> komentar ini? Tindakan ini tidak dapat dibatalkan.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Apakah Anda yakin ingin membatalkan pembuatan acara? Anda akan kehilangan semua modifikasi.",
"Are you sure you want to cancel your participation at event \"{title}\"?": "Apakah Anda yakin ingin membatalkan keikutsertaan Anda dalam acara \"{title}\"?",
"Are you sure you want to delete this event? This action cannot be reverted.": "Apakah Anda yakin ingin menghapus acara ini? Tindakan ini tidak dapat dibatalkan.",
"Ask your instance admin to {enable_feature}.": "Minta admin instansi ini untuk {enable_feature}.",
"Avatar": "Avatar",
"Back to previous page": "Kembali ke halaman sebelumnya",
"Banner": "Banner",
"Begins on": "Dimulai pada",
"Bold": "Tebal",
"Browser notifications": "Notifikasi browser",
"By @{group}": "Oleh @{group}",
"By @{username}": "Oleh @{username}",
"By others": "Dari orang lain",
"By {author}": "Oleh {author}",
"By {group}": "Oleh {group}",
"Can be an email or a link, or just plain text.": "Dapat berbentuk surel atau tautan, atau hanya teks biasa.",
"Cancel": "Batalkan",
"Cancel anonymous participation": "Batalkan keikutsertaan anonim",
"Cancel creation": "Batalkan pembuatan",
"Cancel edition": "Batalkan penyuntingan",
"Cancel my participation request…": "Batalkan permintaan keikutsertaan saya…",
"Cancel my participation…": "Batalkan keikutsertaan saya…",
"Cancelled": "Dibatalkan",
"Cancelled: Won't happen": "Dibatalkan: Tidak akan terjadi",
"Change": "Ubah",
"Change my email": "Ubah surel saya",
"Change my identity…": "Ubah identitas saya…",
"Change my password": "Ubah kata sandi saya",
"Change timezone": "Ubah zona waktu",
"Check your inbox (and your junk mail folder).": "Periksa kotak masuk (inbox) Anda (dan folder junk surat Anda).",
"City or region": "Kota atau wilayah",
"Click for more information": "Klik untuk informasi lebih lanjut",
"Click to upload": "Klik untuk mengunggah",
"Close": "Tutup",
"Close comments for all (except for admins)": "Tutup komentar untuk semua orang (kecuali admin)",
"Closed": "Ditutup",
"Comment deleted": "Komentar dihapus",
"Comment from @{username} reported": "Komentar dari @{username} dilaporkan",
"Comment text can't be empty": "Teks komentar tidak boleh kosong",
"Comments": "Komentar",
"Confirm my participation": "Konfirmasi keikutsertaan saya",
"Confirm my particpation": "Konfirmasi keikutsertaan saya",
"Confirmed": "Dikonfirmasi",
"Confirmed at": "Dikonfirmasi pada",
"Confirmed: Will happen": "Dikonfirmasi: Akan terjadi",
"Congratulations, your account is now created!": "Selamat, akun Anda sudah dibuat!",
"Contact": "Hubungi",
"Continue editing": "Lanjutkan menyunting",
"Cookies and Local storage": "",
"Copy URL to clipboard": "Salin URL ke papan klip",
"Copy details to clipboard": "Salin keterangan ke papan klip",
"Country": "Negara",
"Create": "Buat",
"Create a discussion": "Buat sebuah diskusi",
"Create a new event": "Buat acara baru",
"Create a new group": "Buat kelompok baru",
"Create a new identity": "Buat identitas baru",
"Create a new profile": "Buat profil baru",
"Create a videoconference": "Buat telekonferensi",
"Create an account": "Buat sebuah akun",
"Create event": "Buat acara",
"Create group": "Buat kelompok",
"Create token": "Buat token",
"Created by {name}": "DIbuat oleh {name}",
"Created by {username}": "Dibuat oleh {username}",
"Dashboard": "Dasbor",
"Date": "Tanggal",
"Date and time": "Tanggal dan waktu",
"Date and time settings": "Pengaturan tanggal dan waktu",
"Decline": "Tolak",
"Default": "Default",
"Default Mobilizon terms": "Persyaratan default Mobilizon",
"Delete": "Hapus",
"Delete Comment": "Hapus Komentar",
"Delete Event": "Hapus Acara",
"Delete account": "Hapus Akun",
"Delete conversation": "Hapus percakapan",
"Delete event": "Hapus acara",
"Delete everything": "Hapus segalanya",
"Delete group": "Hapus kelompok",
"Delete my account": "Hapus akun saya",
"Delete post": "Hapus postingan",
"Delete this identity": "Hapus identitas ini",
"Delete your identity": "Hapus identitas Anda",
"Delete {eventTitle}": "Hapus {eventTitle}",
"Delete {preferredUsername}": "Hapus {preferredUsername}",
"Deleting comment": "Menghapus komentar",
"Deleting event": "Menghapus acara",
"Deleting my account will delete all of my identities.": "Menghapus akun saya akan menghapus semua identitas saya.",
"Deleting your Mobilizon account": "Menghapus akun Mobilizon Anda",
"Description": "Keterangan",
"Details": "Keterangan",
"Didn't receive the instructions?": "Tidak menerima instruksinya?",
"Disabled": "Dinonaktifkan",
"Discussions": "Diskusi",
"Do you wish to {create_event} or {explore_events}?": "Apakah Anda ingin {create_event} atau {explore_events}?",
"Do you wish to {create_group} or {explore_groups}?": "Apakah Anda ingin {create_group} atau {explore_groups}?",
"Domain": "Domain",
"Draft": "Draf",
"Drafts": "Draf",
"Edit": "Sunting",
"Edit post": "Sunting postingan",
"Edit profile {profile}": "Sunting profil {profile}",
"Edited {ago}": "Disunting {ago}",
"Edited {relative_time} ago": "Disunting {relative_time} yang lalu",
"Eg: Stockholm, Dance, Chess…": "Contoh: Stockholm, Menari, Catur…",
"Email": "Surel",
"Email address": "Alamat surel",
"Enabled": "Diaktifkan",
"Ends on…": "Berakhir pada…",
"Enter the link URL": "Masukkan URL tautan",
"Enter your email address below, and we'll email you instructions on how to change your password.": "Masukkan alamat surel Anda di bawah, dan kami akan mengirimkan Anda surel instruksi tentang bagaimana cara mengubah kata sandi Anda.",
"Enter your own privacy policy. HTML tags allowed. The {mobilizon_privacy_policy} is provided as template.": "Masukkan kebijakan privasi Anda. HTML tag diizinkan. {mobilizon_privacy_policy} akan disediakan sebagai template.",
"Error": "Terjadi kesalahan",
"Error details copied!": "Keterangan kesalahan disalin!",
"Error message": "Pesan kesalahan",
"Error stacktrace": "Stacktrace kesalahan",
"Error while changing email": "Terjadi kesalahan ketika mengubah surel",
"Error while loading the preview": "Terjadi kesalahan ketika memuat tinjauan",
"Error while login with {provider}. Retry or login another way.": "Terjadi kesalahan ketika ingin masuk dengan {provider}. Coba lagi atau masuk dengan cara lain.",
"Error while reporting group {groupTitle}": "Terjadi kesalahan melaporkan kelompok {groupTitle}",
"Error while validating account": "Terjadi kesalahan ketika memvalidasi akun",
"Error while validating participation request": "Terjadi kesalahan ketika memvalidasi permintaan keikutsertaan",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "Alternatif yang etis untuk Facebook events, groups dan pages, Mobilizon adalah <b>alat yang didesain untuk melayani Anda</b>. Titik.",
"Event": "Acara",
"Event URL": "URL Acara",
"Event already passed": "Acara sudah berlalu",
"Event cancelled": "Acara dibatalkan",
"Event creation": "Pembuatan acara",
"Event edition": "Penyuntingan acara",
"Event list": "Daftar acara",
"Event metadata": "Metadata acara",
"Event page settings": "Pengaturan halaman acara",
"Event {eventTitle} deleted": "Acara {eventTitle} dihapus",
"Event {eventTitle} reported": "Acara {eventTitle} dilaporkan",
"Events": "Acara",
"Events nearby": "Acara-acara terdekat",
"Everything": "Segalanya",
"Ex: mobilizon.fr": "Contoh: mobilizon.fr",
"Ex: someone@mobilizon.org": "Contoh: seseorang@mobilizon.org",
"Explore": "Jelajahi",
"Failed to save admin settings": "Gagal menyimpan pengaturan admin",
"Fediverse account": "Akun fediverse",
"Find an instance": "Cari instansi",
"Find another instance": "Cari instansi lain",
"Follower": "Pengikut",
"Followers": "Pengikut",
"Followings": "Diikuti",
"For instance: London": "Contoh: London",
"For instance: London, Taekwondo, Architecture…": "Contoh: London, Taekwondo, Arsitektur…",
"Forgot your password ?": "Lupa kata sandi Anda ?",
"Forgot your password?": "Lupa kata sandi Anda?",
"From yourself": "Dari Anda",
"Fully accessible with a wheelchair": "Sepenuhnya dapat diakses dengan kursi roda",
"General": "Umum",
"General information": "Informasi umum",
"Getting location": "Mendapatkan lokasi",
"Go to the event page": "Pergi ke halaman acara",
"Group": "Kelompok",
"Group Followers": "Pengiku-pengikut Kelompok",
"Group Members": "Anggota Kelompok",
"Group URL": "URL Kelompok",
"Group activity": "Aktivitas kelompok",
"Group name": "Nama kelompok",
"Group settings": "Pengaturan kelompok",
"Group settings saved": "Pengaturan kelompok disimpan",
"Group short description": "Keterangan singkat kelompok",
"Group visibility": "Visibilitas",
"Group {displayName} created": "Kelompok {displayName} dibuat",
"Group {groupTitle} reported": "Kelompok {groupTitle} dilaporkan",
"Groups": "Kelompok-kelompok",
"Groups are not enabled on this instance.": "Kelompok tidak diaktifkan di instansi ini.",
"Hide replies": "Sembunyikan balasan",
"Home": "Beranda",
"Home to {number} users": "Rumah untuk {number} pengguna",
"I agree to the {instanceRules} and {termsOfService}": "Saya setuju dengan {instanceRules} dan {termsOfService}",
"I don't have a Mobilizon account": "Saya tidak punya akun Mobilizon",
"I have a Mobilizon account": "Saya memiliki akun Mobilizon",
"I have an account on another Mobilizon instance.": "Saya memiliki akun di instansi Mobilizon lain.",
"I participate": "Saya ikut serta",
"I want to allow people to participate without an account.": "Saya ingin mengizinkan orang-orang ikut serta tanpa akun.",
"ICS/WebCal Feed": "Feed ICS/WebCal",
"Identity {displayName} created": "Identitas {displayName} dibuat",
"Identity {displayName} deleted": "Identitas {displayName} dihapus",
"Identity {displayName} updated": "Identitas {displayName} diperbarui",
"If allowed by organizer": "Jika diizinkan oleh penyelenggara",
"If you want, you may send a message to the event organizer here.": "Jika Anda mau, Anda dapat mengirim pesan ke penyelenggara acara di sini.",
"Ignore": "Abaikan",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "Di konteks berikut, sebuah aplikasi adalah suatu perangkat lunak, baik disediakan oleh tim Mobilizon atau oleh pihak ke-3, yang digunakan untuk berinteraksi dengan instansi Anda.",
"Instance": "Instansi",
"Instance Long Description": "Keterangan Panjang Instansi",
"Instance Name": "Nama Instansi",
"Instance Privacy Policy": "Kebijakan Privasi Instansi",
"Instance Privacy Policy Source": "Sumber Kebijakan Privasi Instansi",
"Instance Privacy Policy URL": "URL Kebijakan Privasi Instansi",
"Instance Rules": "Peraturan Instansi",
"Instance Short Description": "Keterangan Singkat Instansi",
"Instance Slogan": "Slogan Instansi",
"Instance administrator": "Administrator instansi",
"Instance configuration": "Konfigurasi instansi",
"Instance languages": "Bahasa-bahasa instansi",
"Instance rules": "Peraturan instansi",
"Instance settings": "Pengaturan instansi",
"Instances following you": "Instansi-instansi yang mengikuti Anda",
"Instances you follow": "Instansi-instansi yang Anda ikuti",
"Invite a new member": "Undang anggota baru",
"Invite member": "Undang anggota",
"Invited": "Diundang",
"Italic": "Miring",
"Join <b>{instance}</b>, a Mobilizon instance": "Bergabung ke <b>{instance}</b>, sebuah instansi Mobilizon",
"Join group {group}": "Bergabung ke kelompok {group}",
"Language": "Bahasa",
"Last IP adress": "Alamat IP terakhir",
"Last week": "Pekan lalu",
"Latest posts": "Postingan-postingan terakhir",
"Learn more": "Pelajari lebih lanjut",
"Learn more about Mobilizon": "Pelajari lebih lanjut tentang Mobilizon",
"Learn more about {instance}": "Pelajari lebih lanjut tentang {instance}",
"Leave": "Keluar",
"Leave event": "Tinggalkan acara",
"Leaving event \"{title}\"": "Meninggalkan acara \"{title}\"",
"Legal": "Legal",
"Let's define a few settings": "Ayo kita atur beberapa pengaturan",
"License": "Lisensi",
"Live": "Siaran Langsung",
"Load more": "Muat lebih banyak",
"Load more activities": "Muat lebih banyak aktivitas",
"Loading comments…": "Memuat komentar…",
"Location": "Lokasi",
"Log in": "Masuk",
"Log out": "Keluar",
"Login": "Masuk",
"Login on Mobilizon!": "Masuk ke Mobilizon!",
"Login on {instance}": "Masuk di {instansi}",
"Login status": "Status login",
"Manage participations": "",
"Member": "Anggota",
"Members": "Anggota",
"Members-only post": "Postingan hanya-anggota",
"Message": "Pesan",
"Mobilizon": "Mobilizon",
"Mobilizon is a tool that helps you <b>find, create and organise events</b>.": "Mobilizon adalah alat yang membantu Anda untuk <b>mencari, membuat dan menyelenggarakan acara-acara</b>.",
"Mobilizon software": "Perangkat lunak Mobilizon",
"Mobilizon version": "Versi Mobilizon",
"Mobilizon will send you an email when the events you are attending have important changes: date and time, address, confirmation or cancellation, etc.": "Mobilizon akan mengirimkan Anda surel ketika acara-acara yang Anda hadiri memiliki perubahan penting: tanggal dan waktu, alamat, konfirmasi atau pembatalan, dll.",
"Moderation": "Moderasi",
"Moderation log": "Log moderasi",
"Moderator": "Moderator",
"Move": "Pindahkan",
"My account": "Akun saya",
"My events": "Acara-acara saya",
"My groups": "Kelompok-kelompok saya",
"My identities": "Identitas-identitas saya",
"Name": "Nama",
"New discussion": "Diskusi baru",
"New email": "Surel baru",
"New folder": "Folder baru",
"New link": "Tautan baru",
"New members": "Anggota baru",
"New note": "Catatan baru",
"New password": "Kata sandi baru",
"New post": "Postingan baru",
"Next": "Berikutnya",
"Next month": "Bulan depan",
"Next page": "Halaman selanjutnya",
"Next week": "Pekan depan",
"No comment": "Tidak ada komentar",
"No comments yet": "Belum ada komentar",
"No discussions yet": "Belum ada diskusi",
"No events found": "Tidak ada acara yang ditemukan",
"No information": "Tidak ada informasi",
"No members found": "Tidak ada anggota yang ditemukan",
"No message": "Tidka ada pesan",
"No moderation logs yet": "Belum ada log moderasi",
"No one is participating|One person participating|{going} people participating": "Tidak ada yang ikut serta|Satu orang ikut serta|{going} orang ikut serta",
"No participant to reject|Reject participant|Reject {number} participants": "Tidak ada peserta yang perlu ditolak|Tolak peserta|Tolak {number} peserta",
"No posts found": "Tak ada postingan yang ditemukan",
"No posts yet": "Belum ada postingan",
"No results for {search}": "Tidak ada hasil untuk {search}",
"No rules defined yet.": "Belum ada peraturan yang ditetapkan.",
"None": "Tidak ada",
"Not accessible with a wheelchair": "Tidak dapat diakses dengan kursi roda",
"Not confirmed": "Belum dikonfirmasi",
"Notes": "Catatan",
"Nothing to see here": "Tidak ada yang perlu dilihat di sini",
"Notification before the event": "Notifikasi sebelum acara",
"Notification on the day of the event": "Notifikasi di hari acara",
"Notification settings": "Pengaturan notifikasi",
"Notifications": "Notifikasi",
"Notify participants": "Kirimkan notifikasi ke para peserta",
"Now, create your first profile:": "Sekarang, buat profil pertama Anda:",
"OK": "Baik",
"Old password": "Kata sandi lama",
"Only accessible through link": "Hanya dapat diakses melalui tautan",
"Only group moderators can create, edit and delete posts.": "Hanya moderator-moderator kelompok yang dapat membuat, menyunting dan menghapus postingan.",
"Open a topic on our forum": "Buat sebuah topik di forum kami",
"Open an issue on our bug tracker (advanced users)": "Buat sebuah masalah di pelacak bug kami (pengguna lanjutan)",
"Organized": "Diselenggarakan",
"Organized by": "Diselenggarakan oleh",
"Organized by you": "Diselenggarakan oleh Anda",
"Organized by {name}": "Diselenggarakan oleh {name}",
"Organizer": "Penyelenggara",
"Organizers": "Penyelenggara-penyelenggara",
"Other": "Lainnya",
"Other actions": "Tindakan lain",
"Other software may also support this.": "Perangkat lunak lain mungkin juga mendukung ini.",
"Page": "Halaman",
"Page not found": "Halaman tidak ditemukan",
"Participant": "Peserta",
"Participants": "Peserta-peserta",
"Participate": "Ikut serta",
"Participate using your email address": "Ikut serta menggunakan alamat surel Anda",
"Participation confirmation": "Konfirmasi keikutsertaan",
"Participations": "Keikutsertaan",
"Password": "Kata sandi",
"Password (confirmation)": "Kata sandi (konfirmasi)",
"Past events": "Acara yang telah lalu",
"PeerTube live": "Siaran langsung PeerTube",
"Pending": "Tertunda",
"Pick a group": "Pilih kelompok",
"Pick an identity": "Pilih identitas",
"Pick an instance": "Pilih sebuah instansi",
"Please add as many details as possible to help identify the problem.": "Harap tambahkan keterangan sebanyak mungkin untuk membantu mengidentifikasi masalahnya.",
"Please check your spam folder if you didn't receive the email.": "Harap periksa folder spam Anda jika Anda tidak menerima surelnya.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Harap hubungi admin instansi Mobilizon ini jika Anda pikir ini adalah sebuah kesalahan.",
"Please enter your password to confirm this action.": "Harap masukkan kata sandi untuk mengkonfirmasi tindakan ini.",
"Please read the {fullRules} published by {instance}'s administrators.": "Harap baca {fullRules} yang dipublikasikan oleh administrator-administrator {instance}.",
"Post": "Postingan",
"Post a comment": "Posting sebuah komentar",
"Post a reply": "Posting sebuah balasan",
"Postal Code": "Kode Pos",
"Posts": "Postingan-postingan",
"Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Didukung oleh {mobilizon}. © 2018 - {date} Kontributor-kontributor Mobilizon - Dibuat dengan dukungan finansial dari {contributors}.",
"Preferences": "Preferensi",
"Previous": "Sebelumnya",
"Previous page": "Halaman sebelumnya",
"Privacy Policy": "Kebijakan Privasi",
"Privacy policy": "Kebijakan privasi",
"Profile": "Profil",
"Promote": "Promosikan",
"Public": "Publik",
"Public RSS/Atom Feed": "Feed RSS/Atom Publik",
"Public event": "Acara publik",
"Publication date": "Tanggal publikasi",
"Publish": "Publikasikan",
"Radius": "Radius",
"Redirecting to content…": "Membawa Anda ke konten…",
"Register": "Daftar",
"Register on this instance": "Daftar di instansi ini",
"Registration is allowed, anyone can register.": "Pendaftaran diizinkan, semua orang dapat mendaftar.",
"Registration is closed.": "Pendaftaran ditutup.",
"Registration is currently closed.": "Pendaftaran ditutup saat ini.",
"Registrations": "Pendaftaran",
"Reject": "Tolak",
"Rejected": "Ditolak",
"Remember my participation in this browser": "Ingat keikutsertaan saya di browser ini",
"Remove": "Hapus",
"Rename": "Ubah nama",
"Reopen": "Buka kembali",
"Replay": "Siaran Ulang",
"Reply": "Balas",
"Report": "Laporkan",
"Report this comment": "Laporkan komentar ini",
"Report this event": "Laporkan acara ini",
"Report this group": "Laporkan kelompok ini",
"Reported": "Dilaporkan",
"Reported by": "Dilaporkan oleh",
"Reported by {reporter}": "Dilaporkan oleh {reporter}",
"Resend confirmation email": "Kirim ulang surel konfirmasi",
"Restricted": "Dibatasi",
"Return to the group page": "Kembali ke halaman kelompok",
"Right now": "Sekarang",
"Rules": "Peraturan",
"SSL/TLS": "SSL/TLS",
"Save": "Simpan",
"Save draft": "Simpan draf",
"Schedule": "Jadwal",
"Search": "Cari",
"Search events, groups, etc.": "Cari acara, kelompok, dll.",
"Searching…": "Mencari…",
"Search…": "Cari…",
"Select a language": "Pilih bahasa",
"Select a radius": "Pilih radius",
"Select a timezone": "Pilih zona waktu",
"Send email": "Kirim surel",
"Send the confirmation email again": "Kirim surel konfirmasi lagi",
"Send the report": "Kirim laporan",
"Set an URL to a page with your own privacy policy.": "Tetapkan URL ke halaman dengan kebijakan privasi Anda.",
"Settings": "Pengaturan",
"Share": "Bagikan",
"Share this event": "Bagikan acara ini",
"Share this group": "Bagikan kelompok ini",
"Short bio": "Bio singkat",
"Show map": "Tampilkan peta",
"Show the time when the event begins": "Tampilkan waktu acara dimulai",
"Show the time when the event ends": "Tampilkan waktu acara berakhir",
"Sign Language": "Bahasa Isyarat",
"Sign in with": "Masuk dengan",
"Sign up": "Daftar",
"Starts on…": "Dimulai pada…",
"Status": "Status",
"Submit": "Kirim",
"Subtitles": "Subtitel",
"Technical details": "Keterangan teknis",
"Tentative": "Tentative",
"Tentative: Will be confirmed later": "Tentative: Akan dikonfirmasi nanti",
"Terms of service": "Persyaratan layanan",
"Text": "Teks",
"The URL where the event can be watched live": "URL di mana acara dapat ditonton secara langsung",
"The actual number of participants may differ, as this event is hosted on another instance.": "Jumlah asli peserta mungkin berbeda, karena acara ini diselenggarakan instansi lain.",
"The draft event has been updated": "Draf acara telah diperbarui",
"The event has been created as a draft": "Acara ini telah dibuat sebagai draf",
"The event has been published": "Acara ini telah dipublikasikan",
"The event has been updated": "Acara ini telah diperbarui",
"The event has been updated and published": "Acara ini telah diperbarui dan dipublikasikan",
"The event live video contains subtitles": "Video siaran langsung acara mengandung subtitel",
"The event live video does not contain subtitles": "Video siaran langsung acara tidak mengandung subtitel",
"The event organizer didn't add any description.": "Penyelenggara acara tidak menambahkan keterangan.",
"The event {event} was created by {profile}.": "Acara {event} dibuat oleh {profile}.",
"The event {event} was deleted by {profile}.": "Acara {event} dihapus oleh {profile}.",
"The event {event} was updated by {profile}.": "Acara {event} diperbarui oleh {profile}.",
"The events you created are not shown here.": "Acara-acara yang Anda buat tidak ditampilkan di sini.",
"The group's avatar was changed.": "Avatar kelompok ini diubah.",
"The group's banner was changed.": "Banner kelompok ini diubah.",
"The group's short description was changed.": "Keterangan singkat kelompok ini diubah.",
"The instance administrator is the person or entity that runs this Mobilizon instance.": "Administrator instansi adalah seseorang atau suatu kesatuan yang menjalankan instansi Mobilizon ini.",
"The organiser has chosen to close comments.": "Penyelenggara telah memilih untuk menutup komentar.",
"The page you're looking for doesn't exist.": "Halaman yang sedang Anda cari tidak ada.",
"The password was successfully changed": "Kata sandi berhasil diubah",
"The post {post} was created by {profile}.": "Postingan {post} dibuat oleh {profile}.",
"The post {post} was deleted by {profile}.": "Postingan {post} dihapus oleh {profile}.",
"The post {post} was updated by {profile}.": "Postingan {post} diperbarui oleh {profile}.",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Laporan akan dikirim ke moderator instansi Anda. Anda dapat menjelaskan mengapa Anda melaporkan konten ini di bawah.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "Gambar yang dipilih terlalu berat. Anda harus memilih sebuah berkas yang lebih kecil dari {size}.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Keterangan teknis dari kesalahan dapat membantu pengembang memecahkan masalahnya dengan lebih mudah. Harap tambahkan keterangan teknisnya ke umpan balik Anda.",
"There are {participants} participants.": "Ada {participants} peserta.",
"There is no activity yet. Start doing some things to see activity appear here.": "Belum ada aktivitas. Mulai melakukan sesuatu untuk melihat aktivitas muncul di sini.",
"There will be no way to recover your data.": "Tidak akan ada cara untuk memulihkan data Anda.",
"These events may interest you": "Acara-acara ini mungkin menarik untuk Anda",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Instansi Mobilizon ini dan penyelenggara ini mengizinkan peserta anonim, tetapi membutuhkan validasi melalui konfirmasi surel.",
"This URL doesn't seem to be valid": "URL tidak terlihat valid",
"This URL is not supported": "URL ini tidak didukung",
"This event has been cancelled.": "Acara ini telah dibatalkan.",
"This event is accessible only through it's link. Be careful where you post this link.": "Acara ini hanya dapat diakses melalui tautannya. Berhati-hatilah di mana Anda memposting tautan ini.",
"This group doesn't have a description yet.": "Kelompok ini belum memiliki keterangan.",
"This group is accessible only through it's link. Be careful where you post this link.": "Kelompok ini hanya dapat diakses melalui tautannya. Berhati-hatilah di mana Anda memposting tautan ini.",
"This group is invite-only": "Kelompok ini adalah kelompok undang-saja",
"This identity is not a member of any group.": "Identitas ini bukan anggota kelompok apapun.",
"This information is saved only on your computer. Click for details": "Informasi ini disimpan hanya di komputer Anda. Klik untuk keterangan",
"This instance hasn't got push notifications enabled.": "Instansi ini belum mengaktifkan notifikasi push.",
"This instance isn't opened to registrations, but you can register on other instances.": "Pendaftaran tidak dibuka untuk instansi ini, tetapi Anda dapat mendaftar di instansi lain.",
"This is a demonstration site to test Mobilizon.": "Ini adalah situs demonstrasi untuk menguji Mobilizon.",
"This month": "Bulan ini",
"This setting will be used to display the website and send you emails in the correct language.": "Pengaturan ini digunakan untuk menampilkan situs web dan mengirimkan Anda surel dengan bahasa yang benar.",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Situs web ini tidak dimoderasi dan data yang Anda masukkan akan secara otomatis dihancurkan setiap hari pada pukul 00:01 (zona waktu Paris).",
"This week": "Pekan ini",
"This weekend": "Akhir pekan ini",
"Timezone": "Zona waktu",
"Timezone detected as {timezone}.": "Zona waktu {timezone} terdeteksi.",
"Title": "Judul",
"To confirm, type your event title \"{eventTitle}\"": "Untuk mengkonfirmasi, ketik judul acara Anda \"{eventTitle}\"",
"To create and manage your events": "Untuk membuat dan mengelola acara-acara Anda",
"To create or join an group and start organizing with other people": "Untuk membuat atau bergabung ke suatu kelompok dan mulai menyelenggarakan bersama orang lain",
"Today": "Hari Ini",
"Tomorrow": "Besok",
"Tools": "Alat",
"Twitch live": "Siaran langsung Twitch",
"Twitter account": "Akun Twitter",
"Type": "Jenis",
"Type or select a date…": "Ketik atau pilih suatu tanggal…",
"URL": "URL",
"URL copied to clipboard": "URL disalin ke papan klip",
"Unable to copy to clipboard": "Tidak dapat menyalin ke papan klip",
"Unable to create the profile. The avatar picture may be too heavy.": "Tidak dapat membuat profil. Gambar avatarnya mungkin terlalu berat.",
"Unable to detect timezone.": "Tidak dapat mendeteksi zona waktu.",
"Unable to save your participation in this browser.": "Gagal menyimpan keikutsertaan Anda di browser ini.",
"Unable to update the profile. The avatar picture may be too heavy.": "Tidak dapat memperbarui profil. Gambar avatarnya mungkin terlalu berat.",
"Unfortunately, this instance isn't opened to registrations": "Sayangnya, pendaftaran tidak terbuka untuk instansi ini",
"Unfortunately, your participation request was rejected by the organizers.": "Sayangnya, permintaan keikutsertaan Anda ditolak oleh penyelenggara.",
"Unknown": "Tak diketahui",
"Unknown error.": "Kesalahan tak dikenal.",
"Unsaved changes": "Perubahan belum tersimpan",
"Upcoming": "Akan datang",
"Upcoming events": "Acara-acara yang akan datang",
"Update": "Perbarui",
"Update app": "Perbarui aplikasi",
"Update event {name}": "Perbarui acara {name}",
"Update group": "Perbarui kelompok",
"Update my event": "Perbarui acara saya",
"Update post": "Perbarui postingan",
"Updated": "Diperbarui",
"Uploaded media size": "Ukuran media yang diunggah",
"Use my location": "Gunakan lokasi saya",
"User": "Pengguna",
"User settings": "Pengaturan pengguna",
"View a reply": "|Lihat satu balasan|Lihat {totalReplies} balasan",
"View account on {hostname} (in a new window)": "Lihat akun di {hostname} (di jendela baru)",
"View all": "Lihat semua",
"View all events": "Lihat semua acara",
"View all posts": "Lihat semua postingan",
"View event page": "Lihat halaman acara",
"View everything": "Lihat semuanya",
"View less": "Lihat lebih sedikit",
"View more": "Lihat lebih banyak",
"View page on {hostname} (in a new window)": "Lihat halaman di {hostname} (di jendela baru)",
"Visibility was set to an unknown value.": "Visibilitas diatur menjadi nilai yang tak dikenal.",
"Visibility was set to public.": "Visibilitas diatur menjadi publik.",
"Visible everywhere on the web": "Terlihat di mana saja di web",
"Visible everywhere on the web (public)": "Terlihat di mana saja di web (publik)",
"Warning": "Peringatan",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Kami meningkatkan perangkat lunak terima kasih kepada umpan balik Anda. Untuk memberitahu kami tentang masalah ini, ada dua kemungkinan (keduanya sayangnya membutuhkan pembuatan akun):",
"We just sent an email to {email}": "Kami baru saja mengirim surel ke {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Kami menggunakan zona waktu Anda untuk memastikan Anda mendapatkan notifikasi untuk suatu acara di waktu yang benar.",
"We will redirect you to your instance in order to interact with this event": "Kami akan membawa Anda ke instansi Anda untul berinteraksi dengan acara ini",
"We will redirect you to your instance in order to interact with this group": "Kami akan membawa Anda ke instansi Anda untuk berinteraksi dengan kelompok ini",
"We'll send you an email one hour before the event begins, to be sure you won't forget about it.": "Kami akan mengirimkan Anda sebuah surel satu jam sebelum acara dimulai, untuk memastikan Anda tidak akan melupakan acaranya.",
"Website": "Situs Web",
"Website / URL": "Situs Web / URL",
"Welcome back {username}!": "Selamat datang kembali {username}!",
"Welcome back!": "Selamat datang kembali!",
"Welcome to Mobilizon, {username}!": "Selamat datang di Mobilizon, {username}!",
"What can I do to help?": "Apa yang dapat saya lakukan untuk membantu?",
"Wheelchair accessibility": "Aksesibilitas kursi roda",
"Whether the event is accessible with a wheelchair": "Apakah acaranya dapat diakses menggunakan kursi roda",
"Whether the event live video is subtitled": "Apakah subtitel tersedia pada video siaran langsung acara",
"Who can view this event and participate": "Siapa yang dapat melihat acara ini dan ikut serta",
"Who can view this post": "Siapa yang dapat melihat postingan ini",
"Who published {number} events": "Yang mempublikasikan {number} acara",
"Why create an account?": "Kenapa buat sebuah akun?",
"Within {number} kilometers of {place}": "|Satu kilometer dari {place}|{number} kilometer dari {place}",
"Write something…": "Tulis sesuatu…",
"Yesterday": "Kemarin",
"You added the member {member}.": "Anda menambahkan anggota {member}.",
"You are not an administrator for this group.": "Anda bukan administrator dari kelompok ini.",
"You are not part of any group.": "Anda tidak dalam kelompok apapun.",
"You are offline": "Anda luring",
"You are participating in this event anonymously": "Anda ikut serta pada acara ini secara anonim",
"You are participating in this event anonymously but didn't confirm participation": "Anda ikut serta pada acara ini secara anonim tetapi Anda tidak mengkonfirmasi keikutsertaan",
"You can add tags by hitting the Enter key or by adding a comma": "Anda dapat menambahkan tag dengan menekan tombol Enter atau dengan menambahkan koma",
"You can't change your password because you are registered through {provider}.": "Anda tidak dapat mengubah kata sandi Anda karena Anda mendaftar melalui {provider}.",
"You can't use push notifications in this browser.": "Anda tidak dapat menggunakan notifikasi push di browser ini.",
"You changed your email or password": "Anda mengubah surel atau kata sandi Anda",
"You created the event {event}.": "Anda membuat acara {acara}.",
"You created the group {group}.": "Anda membuat kelompok {group}.",
"You created the post {post}.": "Anda membuat postingan {post}.",
"You deleted the event {event}.": "Anda menghapus acara {event}.",
"You deleted the post {post}.": "Anda menghapus postingan {post}.",
"You didn't create or join any event yet.": "Anda belum membuat atau bergabung ke suatu acara.",
"You don't follow any instances yet.": "Anda belum mengikuti instansi apapun.",
"You have been invited by {invitedBy} to the following group:": "Anda telah diundang oleh {invitedBy} ke kelompok berikut:",
"You have cancelled your participation": "Anda telah membatalkan keikutsertaan Anda",
"You invited {member}.": "Anda mengundang {member}.",
"You may now close this window, or {return_to_event}.": "Anda dapat menutup jendela ini sekarang, atau {return_to_event}.",
"You need to create the group before you create an event.": "Anda harus membuat sebuah kelompok sebelum Anda membuat acara.",
"You need to login.": "Anda harus masuk.",
"You posted a comment on the event {event}.": "Anda memposting komentar ke acara {event}.",
"You promoted {member} to administrator.": "Anda mempromosikan {member} menjadi administrator.",
"You promoted {member} to moderator.": "Anda mempromosikan {member} menjadi moderator.",
"You replied to a comment on the event {event}.": "Anda membalas suatu komentar di acara {event}.",
"You updated the event {event}.": "Anda memperbarui acara {event}.",
"You updated the group {group}.": "Anda memperbarui kelompok {group}.",
"You updated the member {member}.": "Anda memperbarui anggota {member}.",
"You updated the post {post}.": "Anda memperbarui postingan {post.",
"You were promoted to administrator by {profile}.": "Anda dipromosikan menjadi administrator oleh {profile}.",
"You were promoted to moderator by {profile}.": "Anda dipromosikan menjadi moderator oleh {profile}.",
"You wish to participate to the following event": "Anda ingin ikut serta dalam acara berikut",
"You'll receive a confirmation email.": "Anda akan menerima surel konfirmasi.",
"YouTube live": "Siaran langsung YouTube",
"Your account has been successfully deleted": "Akun Anda berhasil dihapus",
"Your account has been validated": "Akun Anda telah divalidasi",
"Your account is being validated": "Akun Anda sedang di validasi",
"Your account is nearly ready, {username}": "Akun Anda hampir siap, {username}",
"Your current email is {email}. You use it to log in.": "Surel Anda saat ini adalah {email}. Anda menggunakannya untuk masuk.",
"Your email": "Surel Anda",
"Your email has been changed": "Surel Anda telah diubah",
"Your email is being changed": "Surel Anda sedang diubah",
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Surel Anda hanya digunakan untuk mengkonfirmasi bahwa Anda benar-benar seseorang dan untuk mengirimkan Anda kabar tentang acara ini. Itu TIDAK akan dibagikan ke instansi lain atau ke penyelengi acara.",
"Your participation has been confirmed": "Keikutsertaan Anda telah dikonfirmasi",
"Your participation has been rejected": "Keikutsertaan Anda telah ditolak",
"Your participation has been requested": "",
"Your participation request has been validated": "Keikutsertaan Anda telah divalidasi",
"Your participation request is being validated": "Keikutsertaan Anda sedang divalidasi",
"Your participation status has been changed": "Status keikutsertaan Anda telah diubah",
"Your participation status is saved only on this device and will be deleted one month after the event's passed.": "Status keikutsertaan Anda disimpan hanya di perangkat ini dan akan dihapus satu bulan setelah acara berlalu.",
"Your timezone is currently set to {timezone}.": "Zona waktu Anda saat ini ditetapkan menjadi {timezone}.",
"Your timezone {timezone} isn't supported.": "Zona waktu Anda {timezone} tidak didukung.",
"[This comment has been deleted by it's author]": "[Komentar ini telah dihapus oleh penulisnya]",
"[This comment has been deleted]": "[Komentar ini telah dihapus]",
"[deleted]": "[dihapus]",
"as {identity}": "sebagai {identity}",
"create a group": "buat kelompok",
"create an event": "membuat sebuah acara",
"default Mobilizon privacy policy": "kebijakan privasi default Mobilizon",
"enable the feature": "mengaktifkan fiturnya",
"explore the events": "jelajahi acara-acara",
"explore the groups": "jelajahi kelompok-kelompok",
"iCal Feed": "Feed iCal",
"instance rules": "peraturan instansi",
"more than 1360 contributors": "lebih dari 1360 kontributor",
"profile@instance": "profil@instansi",
"report #{report_number}": "laporan #{report_number}",
"return to the event's page": "kembali ke halaman acara",
"terms of service": "persyaratan layanan",
"with another identity…": "dengan identitas lain…",
"{count} km": "{count} km",
"{count} members": "Tidak ada anggota|Satu anggota|{count} anggota",
"{count} participants": "Belum ada peserta | Satu peserta | {count} peserta",
"{count} requests waiting": "{count} permintaan menunggu",
"{count} team members": "{count} anggota tim",
"{group}'s events": "Acara-acara {group}",
"{instanceName} is an instance of the {mobilizon} software.": "{instanceName} adalah sebuah instansi perangkat lunak {mobilizon}.",
"{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} adalah sebuah instansi {mobilizon_link}, perangkat lunak gratis dari masyarakat.",
"{member} was invited by {profile}.": "{member} diundang oleh {profile}.",
"{moderator} added a note on {report}": "{moderator} menambahkan catatan pada {report}",
"{moderator} deleted an event named \"{title}\"": "{moderator} menghapus acara bernama \"{title}\"",
"{moderator} has deleted a comment from {author}": "{moderator} telah menghapus sebuah komentar dari {author}",
"{moderator} has deleted a comment from {author} under the event {event}": "{moderator} telah menghapus sebuah komentar dari {author} di acara {event}",
"{moderator} has deleted user {user}": "{moderator} telah menghapus pengguna {user}",
"{moderator} has done an unknown action": "{moderator} telah melakukan tindakan yang tak dikenal",
"{moderator} marked {report} as resolved": "{moderator} menandai {report} sebagai selesai",
"{nb} km": "{nb} km",
"{number} members": "{number} anggota",
"{number} participations": "Tidak pernah ikut serta|Satu keikutsertaan|{number} keikutsertaan",
"{number} posts": "Tidak ada postingan|Satu postingan|{number} postingan",
"{old_group_name} was renamed to {group}.": "{old_group_name} telah berubah nama menjadi {group}.",
"{profile} added the member {member}.": "{profile} menambahkan anggota {member}.",
"{profile} created the group {group}.": "{profile} membuat kelompok {group}.",
"{profile} posted a comment on the event {event}.": "{profile} memposting komentar di acara {event}.",
"{profile} promoted {member} to administrator.": "{profile} mempromosikan {member} menjadi administrator.",
"{profile} promoted {member} to moderator.": "{profile} mempromosikan {member} menjadi moderator.",
"{profile} replied to a comment on the event {event}.": "{profile} membalas suatu komentar di acara {event}.",
"{profile} updated the group {group}.": "{profile} memperbarui kelompok {group}.",
"{profile} updated the member {member}.": "{profile} memperbarui anggota {member}.",
"{username} was invited to {group}": "{username} diundang ke {group}",
"© The OpenStreetMap Contributors": "© Kontributor-kontributor OpenStreetMap"
}

View File

@ -4,6 +4,7 @@
"(this folder)": "(эта папка)",
"(this link)": "(эта ссылка)",
"+ Add a resource": "+ Добавить ресурс",
"+ Create a post": "+ Создать пост",
"+ Create an event": "+ Создать мероприятие",
"+ Post a public message": "+ Опубликовать публичное сообщение",
"+ Start a discussion": "+ Начать обсуждение",
@ -16,8 +17,12 @@
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "Cookie - это небольшой файл, содержащий информацию, которая сохраняется на вашем компьютере при посещении веб-сайта. Когда вы снова посещаете эту страницу, cookie позволяет сайту распознавать ваш браузер. Cookie могут хранить настройки пользователя и другую информацию. Вы можете настроить свой браузер так, чтобы он не принимал файлы cookie. Однако это может привести к тому, что часть функционала веб-сайта не будет работать. Локальное хранилище работает так же, но позволяет хранить больше данных.",
"A discussion has been created or updated": "Обсуждение было создано или обновлено",
"A federated software": "Федеративное программное обеспечение",
"A fediverse account URL to follow for event updates": "URL-адрес учетной записи fediverse, для отслеживания изменений в мероприятиях",
"A link to a page presenting the event schedule": "Ссылка на расписание мероприятий",
"A link to a page presenting the price options": "Ссылка на прайслист",
"A member has been updated": "Участник обновлён",
"A member requested to join one of my groups": "Участник попросил присоединиться к одной из моих групп",
"A new version is available.": "Доступна новая версия.",
"A place for your code of conduct, rules or guidelines. You can use HTML tags.": "Тут можно разместить общие правила, положения или руководства. Вы можете использовать HTML-теги.",
"A place to explain who you are and the things that set your instance apart. You can use HTML tags.": "Тут можно подробно описать кто вы и что делает этот узел особенным. Вы можете использовать HTML-теги.",
"A place to publish something to the whole world, your community or just your group members.": "Место для публикации чего-либо для всего мира, вашего сообщества или только участников вашей группы.",
@ -27,6 +32,7 @@
"A practical tool": "Удобный инструмент",
"A resource has been created or updated": "Ресурс был создан или обновлён",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Короткий слоган для домашней страницы вашего узла. По умолчанию: \"Собирать ⋅ Организовывать ⋅ Мобилизовывать\"",
"A twitter account handle to follow for event updates": "Идентификатор учетной записи Twitter, для слежения за обновлениями мероприятий",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Удобный, свободный и этичный инструмент для объединения, организации и мобилизации.",
"A validation email was sent to {email}": "Письмо с подтверждением было отправлено на адрес {email}",
"API": "API",
@ -39,11 +45,13 @@
"About {instance}": "О {instance}",
"Accept": "Принять",
"Accepted": "Принято",
"Accessibility": "Доступность",
"Accessible only to members": "Доступно только участникам",
"Accessible through link": "Доступно по ссылке",
"Account": "Учётная запись",
"Actions": "Действия",
"Activate browser notification": "Активировать уведомления в браузере",
"Activate browser push notifications": "Активировать push-уведомления в браузере",
"Activated": "Активирован",
"Active": "Активный",
"Activity": "Активность",
@ -57,6 +65,7 @@
"Add a todo": "Добавить в список задач",
"Add an address": "Добавить адрес",
"Add an instance": "Добавить узел",
"Add new…": "Добавить новый…",
"Add some tags": "Добавить теги",
"Add to my calendar": "Добавить в мой календарь",
"Additional comments": "Дополнительные комментарии",
@ -69,6 +78,8 @@
"All the places have already been taken": "Все места уже заняты",
"Allow all comments from users with accounts": "Разрешить все комментарии от авторизованных пользователей",
"Allow registrations": "Разрешить регистрацию",
"An URL to an external ticketing platform": "URL-адрес внешней платформы продажи билетов",
"An error has occured while refreshing the page.": "Произошла ошибка при обновлении страницы.",
"An error has occured. Sorry about that. You may try to reload the page.": "Произошла ошибка. Приносим вам тысячу извинений. Вы можете попробовать перезагрузить страницу.",
"An ethical alternative": "Этичная альтернатива",
"An event I'm going to has been updated": "Мероприятие, на которое я собираюсь, обновлено",
@ -81,6 +92,7 @@
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "Узел - это программное обеспечение Mobilizon, установленное на сервере. Узел может быть запущен кем угодно, использующим {mobilizon_software} или другие федеративные приложения, называемые «Федиверзум». Имя этого узла - {instance_name}. Mobilizon - это федеративная сеть, состоящая из нескольких узлов (как почтовые серверы). Пользователи могут свободно общаться друг с другом, даже если они зарегистрированы на разных узлах.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Интерфейс прикладного программирования» или «API» - это протокол связи, который позволяет программным компонентам взаимодействовать друг с другом. Например, API Mobilizon позволяет стороннему программному обеспечению взаимодействовать с узлами Mobilizon для выполнения определенных действий, таких как автоматическая публикация мероприятий от вашего имени.",
"And {number} comments": "И {number} комментариев",
"Announcements and mentions notifications are always sent straight away.": "Уведомления об объявлениях и упоминаниях всегда отправляются незамедлительно.",
"Anonymous participant": "Анонимный участник",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Анонимные участники получат запрос на подтверждение своего участия по электронной почте.",
"Anonymous participations": "Анонимное участие",
@ -100,6 +112,7 @@
"Are you sure you want to delete this entire discussion?": "Вы уверены, что хотите полностью удалить это обсуждение?",
"Are you sure you want to delete this event? This action cannot be reverted.": "Вы уверены, что хотите удалить это мероприятие? Это действие нельзя отменить.",
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted.": "Поскольку организатор мероприятия решил вручную подтверждать запросы на участие, ваше участие будет фактически подтверждено после того, как вы получите электронное письмо о том, что оно было одобрено.",
"Ask your instance admin to {enable_feature}.": "Попросите администратора вашего узла {enable_feature}.",
"Assigned to": "Присвоен",
"Atom feed for events and posts": "Atom лента для мероприятий и публикаций",
"Avatar": "Аватар",
@ -108,6 +121,8 @@
"Before you can login, you need to click on the link inside it to validate your account.": "Перед тем как войти в систему, вы должны перейти по указанной в письме ссылке, чтобы подтвердить свою учетную запись.",
"Begins on": "Начало",
"Bold": "Жирный",
"Booking": "Бронирование",
"Breadcrumbs": "Хлебные крошки",
"Browser notifications": "Уведомления в браузере",
"By @{group}": "Из @{group}",
"By @{username}": "От @{username}",
@ -152,6 +167,7 @@
"Contact": "Контакт",
"Continue editing": "Продолжить редактирование",
"Cookies and Local storage": "Файлы cookie и локальное хранилище",
"Copy URL to clipboard": "Скопировать URL в буфер обмена",
"Copy details to clipboard": "Копировать подробности в буфер обмена",
"Country": "Страна",
"Create": "Создать",
@ -215,6 +231,7 @@
"Deleting your Mobilizon account": "Удаление моей учётной записи Mobilizon",
"Demote": "Понизить",
"Description": "Описание",
"Details": "Подробности",
"Didn't receive the instructions?": "Не получили инструкции?",
"Disabled": "Отключено",
"Discussions": "Обсуждения",
@ -234,11 +251,14 @@
"Edit post": "Редактировать пост",
"Edit profile {profile}": "Редактировать профиль {profile}",
"Edited {ago}": "Изменено {ago}",
"Edited {relative_time} ago": "Изменено {relative_time} назад",
"Eg: Stockholm, Dance, Chess…": "Например: Москва, танцы, шахматы…",
"Either on the {instance} instance or on another instance.": "На узле {instance}, либо на другом.",
"Either the account is already validated, either the validation token is incorrect.": "Учетная запись уже активирована или проверочный токен недействителен.",
"Either the email has already been changed, either the validation token is incorrect.": "Адрес электронной почты уже был изменён или проверочный токен недействителен.",
"Either the participation request has already been validated, either the validation token is incorrect.": "Заявка на участие уже подтверждена или проверочный токен недействителен.",
"Element title": "Название элемента",
"Element value": "Значение элемента",
"Email": "Электронная почта",
"Email address": "Адрес электронной почты",
"Email notifications": "Уведомления по электронной почте",
@ -257,16 +277,20 @@
"Error while login with {provider}. Retry or login another way.": "Не удалось войти через {provider}. Повторите попытку или войдите другим способом.",
"Error while login with {provider}. This login provider doesn't exist.": "Не удалось войти через {provider}. Такого провайдера аутентификации не существует.",
"Error while reporting group {groupTitle}": "Ошибка при отправке отчёта о группе {groupTitle}",
"Error while subscribing to push notifications": "Ошибка при подписке на push-уведомления",
"Error while suspending group": "Ошибка при блокировке группы",
"Error while updating participation status inside this browser": "Ошибка при обновлении статуса участия в этом браузере",
"Error while validating account": "Ошибка подтверждения учётной записи",
"Error while validating participation request": "Произошла ошибка при проверке заявки на участие",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "Mobilizon - это этичная альтернатива мероприятиям, группам и страницам Facebook. Это <b>инструмент, созданный для вас</b>. И точка.",
"Event": "Мероприятие",
"Event URL": "URL мероприятия",
"Event already passed": "Мероприятие уже прошло",
"Event cancelled": "Мероприятие отменено",
"Event creation": "Создание мероприятия",
"Event edition": "Редактирования мероприятия",
"Event list": "Список мероприятий",
"Event metadata": "Метаданные мероприятия",
"Event page settings": "Настройки страницы мероприятия",
"Event to be confirmed": "Мероприятие должно быть подтверждено",
"Event {eventTitle} deleted": "Мероприятие {eventTitle} удалено",
@ -283,10 +307,14 @@
"Featured events": "Избранные мероприятия",
"Federated Group Name": "Имя федеративной группы",
"Federation": "Федерализация",
"Fediverse account": "Аккаунт Fediverse",
"Fetch more": "Загрузить больше",
"Filter by name": "Фильтр по имени",
"Filter by profile or group name": "Фильтр по профилю или названию группы",
"Find an address": "Найти адрес",
"Find an instance": "Найти узел",
"Find another instance": "Найти другой узел",
"Find or add an element": "Найти или добавить элемент",
"Follower": "Подписчик",
"Followers": "Подписчики",
"Followers will receive new public events and posts.": "Подписчики будут оповещены о новых публичных мероприятиях и публикациях.",
@ -295,10 +323,12 @@
"For instance: London, Taekwondo, Architecture…": "Например: Москва, йога, архитектура…",
"Forgot your password ?": "Забыли свой пароль?",
"Forgot your password?": "Забыли свой пароль?",
"Framadate poll": "Опрос Framadate",
"From the {startDate} at {startTime} to the {endDate}": "От {startDate}, {startTime} до {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "От {startDate}, {startTime} до {endDate}, {endTime}",
"From the {startDate} to the {endDate}": "От {startDate} до {endDate}",
"From yourself": "От себя",
"Fully accessible with a wheelchair": "Полностью доступно для инвалидов-колясочников",
"Gather ⋅ Organize ⋅ Mobilize": "Объединять ⋅ Организовывать ⋅ Мобилизовывать",
"General": "Общая",
"General information": "Общая информация",
@ -311,6 +341,7 @@
"Group": "Группа",
"Group Followers": "Группа подписчиков",
"Group Members": "Участники группы",
"Group URL": "URL группы",
"Group activity": "Групповая активность",
"Group address": "Адрес группы",
"Group display name": "Отображаемое имя группы",
@ -351,6 +382,7 @@
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Если вас спрашивают ваш федеративный идентификатор, то он состоит из вашего имени пользователя и имени вашего узла. Например, федеративный идентификатор для вашего первого профиля:",
"If you have opted for manual validation of participants, Mobilizon will send you an email to inform you of new participations to be processed. You can choose the frequency of these notifications below.": "Если вы выбрали одобрение участников вручную, Mobilizon отправит вам электронное письмо о новых заявках на участие. Ниже вы можете выбрать, как часто вы желаете получать эти уведомления.",
"If you want, you may send a message to the event organizer here.": "Здесь вы можете отправить сообщение организатору мероприятия.",
"Ignore": "Игнорировать",
"In the following context, an application is a software, either provided by the Mobilizon team or by a 3rd-party, used to interact with your instance.": "Приложение в этом контексте - это программное обеспечение, предоставленное командой Mobilizon или третьей стороной, которое используется для взаимодействия с вашим узлом.",
"Instance": "Узел",
"Instance Long Description": "Подробное описание узла",
@ -373,6 +405,7 @@
"Instances": "Узлы",
"Instances following you": "Узлы, подписанные на вас",
"Instances you follow": "Узлы, на которые вы подписаны",
"Integrate this event with 3rd-party tools and show metadata for the event.": "Интегрировать это мероприятие со сторонними сервисами и просмотреть его метаданные.",
"Interact with a remote content": "Взаимодействовать с удаленным контентом",
"Invite a new member": "Пригласить нового участника",
"Invite member": "Пригласить участника",
@ -381,6 +414,7 @@
"Italic": "Курсив",
"Join <b>{instance}</b>, a Mobilizon instance": "Присоединиться к <b>{instance}</b>, узлу Mobilizon",
"Join group": "Вступить в группу",
"Join group {group}": "Вступить в группу {group}",
"Keep the entire conversation about a specific topic together on a single page.": "Храните всю беседу по определенной теме на одной странице.",
"Key words": "Ключевые слова",
"Language": "Язык",
@ -402,6 +436,7 @@
"License": "Лицензия",
"Limited number of places": "Ограниченное количество мест",
"List title": "Заголовок списка",
"Live": "Живой",
"Load more": "Загрузить больше",
"Load more activities": "Загрузить больше действий",
"Loading comments…": "Загрузка комментариев…",
@ -421,6 +456,7 @@
"Mark as resolved": "Отметить как решённое",
"Member": "Участник",
"Members": "Участники",
"Members-only post": "Пост только для участников",
"Mentions": "Упоминания",
"Message": "Сообщение",
"Mobilizon": "Mobilizon",
@ -500,8 +536,10 @@
"No resources selected": "Ресурсы не выбраны|Выбран один ресурс|Выбрано {count} ресурсов",
"No resources yet": "Ресурсов пока нет",
"No results for \"{queryText}\"": "Нет результатов по запросу \"{queryText}\"",
"No results for {search}": "Нет результатов по запросу {search}",
"No rules defined yet.": "Правила еще не определены.",
"None": "Никто",
"Not accessible with a wheelchair": "Не доступно для инвалидов-колясочников",
"Not approved": "Не одобрено",
"Not confirmed": "Не подтверждено",
"Notes": "Примечания",
@ -521,11 +559,14 @@
"On {date} from {startTime} to {endTime}": "{date} c {startTime} до {endTime}",
"On {date} starting at {startTime}": "{date}, начало в {startTime}",
"On {instance}": "На {instance}",
"On {instance} and other federated instances": "В {instance} и других федеративных узлах",
"Online ticketing": "Продажа билетов онлайн",
"Only accessible through link": "Доступно только по ссылке",
"Only accessible through link (private)": "Доступно только по ссылке (приватно)",
"Only accessible to members of the group": "Доступно только участникам группы",
"Only alphanumeric lowercased characters and underscores are supported.": "Допустимы только буквенно-цифровые символы нижнего регистра и подчеркивания.",
"Only group members can access discussions": "Только участники группы имеют доступ к обсуждениям",
"Only group moderators can create, edit and delete events.": "Только модераторы группы могут создавать, редактировать и удалять мероприятия.",
"Only group moderators can create, edit and delete posts.": "Только модераторы группы могут создавать, редактировать и удалять публикации.",
"Open": "Открыто",
"Open a topic on our forum": "Откройте тему на нашем форуме",
@ -540,6 +581,7 @@
"Organizer notifications": "Уведомления организатора",
"Organizers": "Организаторы",
"Other": "Другой",
"Other actions": "Другие действия",
"Other notification options:": "Другие настройки уведомлений:",
"Other software may also support this.": "Другое программное обеспечение также может поддерживать это.",
"Otherwise this identity will just be removed from the group administrators.": "В противном случае этот идентификатор будет удалён у администраторов группы.",
@ -547,6 +589,7 @@
"Page limited to my group (asks for auth)": "Страница предназначена только для моей группы (требуется авторизация)",
"Page not found": "Страница не найдена",
"Parent folder": "Родительская папка",
"Partially accessible with a wheelchair": "Частично доступно для инвалидов-колясочников",
"Participant": "Участник",
"Participants": "Участники",
"Participate": "Принять участие",
@ -560,6 +603,8 @@
"Password (confirmation)": "Пароль (подтверждение)",
"Password reset": "Сброс пароля",
"Past events": "Прошедшие мероприятия",
"PeerTube live": "Стрим на PeerTube",
"PeerTube replay": "Воспроизведение на PeerTube",
"Pending": "В ожидании",
"Personal feeds": "Личные ленты",
"Pick": "Выбрать",
@ -583,6 +628,7 @@
"Preferences": "Персональные настройки",
"Previous": "Предыдущий",
"Previous page": "Предыдущая страница",
"Price sheet": "Прайс лист",
"Privacy Policy": "Политика конфиденциальности",
"Privacy policy": "Политика конфиденциальности",
"Private event": "Приватное мероприятие",
@ -599,6 +645,7 @@
"Public feeds": "Публичные ленты",
"Public iCal Feed": "Публичная iCal-лента",
"Public page": "Публичная страница",
"Public preview": "Публичный предварительный просмотр",
"Publication date": "Дата публикации",
"Publish": "Опубликовать",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Опубликованные мероприятия с <b>{comments}</b> комментариями и <b>{participations}</b> подтвержденными участниками",
@ -606,7 +653,9 @@
"RSS/Atom Feed": "RSS/Atom новостная лента",
"Radius": "Радиус",
"Recap every week": "Подводить итоги каждую неделю",
"Receive one email for each activity": "Получать одно электронное письмо для каждого действия",
"Receive one email per request": "Получать электронное письмо на каждый запрос",
"Redirecting in progress…": "Выполняется перенаправление…",
"Redirecting to content…": "Перенаправление к содержимому…",
"Refresh profile": "Обновить профиль",
"Regenerate new links": "Восстановить новые ссылки",
@ -626,6 +675,7 @@
"Rename": "Переименовать",
"Rename resource": "Переименовать ресурс",
"Reopen": "Открыть заново",
"Replay": "Воспроизвести",
"Reply": "Ответ",
"Report": "Жалоба",
"Report #{reportNumber}": "Отчёт #{reportNumber}",
@ -654,6 +704,7 @@
"SSL/TLS": "SSL/TLS",
"Save": "Сохранить",
"Save draft": "Сохранить черновик",
"Schedule": "Расписание",
"Search": "Поиск",
"Search events, groups, etc.": "Искать мероприятия, группы и т. п.",
"Searching…": "Поиск…",
@ -664,6 +715,7 @@
"Select languages": "Выберите языки",
"Select the activities for which you wish to receive an email or a push notification.": "Выберите действия, для которых вы хотите получать электронные письма или push-уведомления.",
"Send email": "Отправить электронное письмо",
"Send notification e-mails": "Отправлять уведомления по электронной почте",
"Send the confirmation email again": "Отправьте письмо с подтверждением еще раз",
"Send the report": "Отправить отчёт",
"Set an URL to a page with your own privacy policy.": "Укажите URL-адрес страницы с вашей собственной политикой конфиденциальности.",
@ -674,17 +726,21 @@
"Share this group": "Поделиться этой группой",
"Short bio": "Коротко о себе",
"Show map": "Показать карту",
"Show me where I am": "Показать моё местоположение",
"Show remaining number of places": "Показать оставшееся количество мест",
"Show the time when the event begins": "Показать время начала мероприятия",
"Show the time when the event ends": "Показать время окончания мероприятия",
"Sign Language": "Сурдоперевод",
"Sign in with": "Войти в систему с",
"Sign up": "Зарегистрироваться",
"Since you are a new member, private content can take a few minutes to appear.": "Поскольку вы новый участник, может потребоваться несколько минут, чтобы приватный контент стал видимым.",
"Social": "Социальный",
"Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Некоторые термины, технические или иные, используемые в приведенном ниже тексте, могут охватывать трудные для понимания концепции. Мы подготовили глоссарий чтобы помочь вам лучше их освоить:",
"Starts on…": "Начало…",
"Status": "Статус",
"Street": "Улица",
"Submit": "Отправить",
"Subtitles": "Субтитры",
"Suspend": "Заблокировать",
"Suspend group": "Заблокировать группу",
"Suspended": "Приостановлен",
@ -695,14 +751,21 @@
"Terms": "Условия",
"Terms of service": "Условия обслуживания",
"Text": "Текст",
"The URL of a poll where the choice for the event date is happening": "URL-адрес опроса, в котором происходит выбор даты мероприятия",
"The URL where the event can be watched live": "URL-адрес, по которому мероприятие можно посмотреть в прямом эфире",
"The URL where the event live can be watched again after it has ended": "URL-адрес, по которому событие транслируется в прямом эфире, можно будет просмотреть снова после его завершения",
"The account's email address was changed. Check your emails to verify it.": "Почтовый адрес аккаунта был изменён. Проверьте свою электронную почту, чтобы убедиться в этом.",
"The actual number of participants may differ, as this event is hosted on another instance.": "Фактическое количество участников может отличаться, так как это мероприятие проводится на другом узле.",
"The content came from another server. Transfer an anonymous copy of the report?": "Контент пришел с другого сервера. Переслать анонимную копию отчета?",
"The draft event has been updated": "Черновик мероприятия обновлён",
"The event has a sign language interpreter": "На мероприятии есть сурдопереводчик",
"The event has been created as a draft": "Мероприятие было создано в виде черновика",
"The event has been published": "Мероприятие опубликовано",
"The event has been updated": "Мероприятие обновлено",
"The event has been updated and published": "Мероприятие обновлено и опубликовано",
"The event hasn't got a sign language interpreter": "На мероприятии нет сурдопреводчика",
"The event live video contains subtitles": "Прямая трансляция мероприятия содержит субтитры",
"The event live video does not contain subtitles": "Прямая трансляция мероприятия не содержит субтитров",
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "Организатор мероприятия решил подтверждать участие вручную. Хотели бы вы добавить короткую заметку, объясняющую, почему вы хотите участвовать в этом мероприятии?",
"The event organizer didn't add any description.": "Организатор мероприятия не добавил описания.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "Организатор мероприятия утверждает участие вручную. Поскольку вы выбрали участие без учётной записи, объясните, почему вы хотите участвовать в этом мероприятии.",
@ -744,6 +807,7 @@
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Эти ленты содержат данные о мероприятиях, участником или создателем которых является любой из ваших профилей. Вы должны держать их в секрете. Вы можете найти ленты для конкретного профиля на его странице с настройками.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Эти ленты содержат данные о мероприятиях, участником или создателем которых является данный профиль. Вы должны держать их в секрете. Вы можете найти ленты для всех ваших профилей в настройках уведомлений.",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Этот узел Mobilizon и этот организатор мероприятия допускают анонимное участие, но требуют подтверждения по электронной почте.",
"This URL doesn't seem to be valid": "Этот URL-адрес кажется недействительным",
"This URL is not supported": "Этот URL не поддерживается",
"This event has been cancelled.": "Это мероприятие было отменено.",
"This event is accessible only through it's link. Be careful where you post this link.": "Это мероприятие доступно только по ссылке. Будьте осторожны, когда публикуете её.",
@ -753,11 +817,13 @@
"This identifier is unique to your profile. It allows others to find you.": "Этот идентификатор уникален для вашего профиля. Он даёт возможность другим найти вас.",
"This identity is not a member of any group.": "Этот идентификатор не является членом какой-либо группы.",
"This information is saved only on your computer. Click for details": "Эта информация сохраняется только на вашем компьютере. Нажмите, чтобы узнать подробности",
"This instance hasn't got push notifications enabled.": "На этом узле не включены push-уведомления.",
"This instance isn't opened to registrations, but you can register on other instances.": "Этот узел не позволяет регистрироваться, но вы можете зарегистрироваться на других.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Этот узел, <b>{instanceName} ({domain})</b>, содержит ваш профиль, поэтому запомните его имя.",
"This is a demonstration site to test Mobilizon.": "Это демонстрационная площадка для тестирования Mobilizon.",
"This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.": "Подобно федеративному имени пользователя (<code>{username}</code>) для групп. Это дает возможность найти группу во всей сети и гарантирует ее уникальность.",
"This month": "В этом месяце",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Этот пост доступен только для участников. У вас есть доступ к нему для модерации только потому, что вы являетесь модератором узла.",
"This setting will be used to display the website and send you emails in the correct language.": "Этот параметр используется для отображения веб-сайта и отправки вам электронных писем на соответствующем языке.",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Эта веб-сайт не модерируется, и любые введенные вами данные будут автоматически удаляться каждый день в 00:01 (время по Парижу).",
"This week": "На этой неделе",
@ -775,8 +841,12 @@
"To register for an event by choosing one of your identities": "Чтобы зарегистрироваться на мероприятие, выбрав один из ваших идентификаторов",
"Today": "Сегодня",
"Tomorrow": "Завтра",
"Tools": "Инструменты",
"Transfer to {outsideDomain}": "Перенос в {outsideDomain}",
"Triggered profile refreshment": "Запуск обновления профиля",
"Twitch live": "Стрим на Twitch",
"Twitch replay": "Воспроизведение на Twitch",
"Twitter account": "Аккаунт Twitter",
"Type": "Тип",
"Type or select a date…": "Введите или выберите дату…",
"URL": "URL-адрес",
@ -798,10 +868,12 @@
"Unset group": "Отменить выбор группы",
"Unsubscribe to WebPush": "Отписаться от WebPush уведомлений",
"Unsubscribe to browser notifications": "Отписаться от уведомлений в браузере",
"Unsubscribe to browser push notifications": "Отказаться от подписки на push-уведомления браузера",
"Unsuspend": "Отменить приостановку",
"Upcoming": "Предстоящие",
"Upcoming events": "Предстоящие мероприятия",
"Update": "Обновить",
"Update app": "Обновить приложение",
"Update event {name}": "Обновить мероприятие {name}",
"Update group": "Обновить группу",
"Update my event": "Обновить моё мероприятие",
@ -814,11 +886,14 @@
"Username": "Имя пользователя",
"Users": "Пользователи",
"View a reply": "|Посмотреть один ответ|Посмотреть {totalReplies} ответов",
"View account on {hostname} (in a new window)": "Просмотреть аккаунт на {hostname} (в новом окне)",
"View all": "Посмотреть всё",
"View all events": "Посмотреть все мероприятия",
"View all posts": "Просмотреть все публикации",
"View event page": "Просмотреть страницу мероприятия",
"View everything": "Посмотреть всё",
"View less": "Показать меньше",
"View more": "Показать больше",
"View page on {hostname} (in a new window)": "Просмотреть страницу на {hostname} (в новом окне)",
"Visibility was set to an unknown value.": "Видимость изменена на неизвестное значение.",
"Visibility was set to private.": "Видимость изменена на приватную.",
@ -827,6 +902,7 @@
"Visible everywhere on the web (public)": "Видно во всем Интернете (публично)",
"Waiting for organization team approval.": "Ожидает одобрения организаторами.",
"Warning": "Предупреждение",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Нам не удалось сохранить ваше участие в этом браузере. Не волнуйтесь, вы успешно подтвердили свое участие, мы просто не смогли сохранить его статус в этом браузере из-за технической проблемы.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Мы улучшаем это программное обеспечение благодаря вашим отзывам. У вас есть два способа сообщить нам об этой проблеме (оба, увы, требуют создания учетной записи пользователя):",
"We just sent an email to {email}": "Мы отправили электронное письмо на адрес {email}",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Мы используем настройки вашего часового пояса, чтобы вы своевременно получали уведомления о мероприятиях.",
@ -836,11 +912,16 @@
"We'll use your timezone settings to send a recap of the morning of the event.": "Мы используем настройки вашего часового пояса, чтобы утром отправить вам напоминание о мероприятии.",
"Website": "Веб-сайт",
"Website / URL": "Веб-сайт / URL",
"Weekly email summary": "Еженедельная сводка по электронной почте",
"Welcome back {username}!": "С возвращением, {username}!",
"Welcome back!": "С возвращением!",
"Welcome to Mobilizon, {username}!": "Добро пожаловать в Mobilizon, {username}!",
"What can I do to help?": "Чем я могу помочь?",
"Wheelchair accessibility": "Доступность для инвалидов-колясочников",
"When a moderator from the group creates an event and attributes it to the group, it will show up here.": "Когда модератор группы создаёт событие и назначает его группе, оно появляется здесь.",
"Whether the event is accessible with a wheelchair": "Если мероприятие доступно для участников в инвалидных колясках",
"Whether the event is interpreted in sign language": "Сопровождается ли мероприятие сурдопереводом",
"Whether the event live video is subtitled": "Есть ли субтитры для прямой трансляции мероприятия",
"Who can view this event and participate": "Кто может просматривать и участвовать в мероприятии",
"Who can view this post": "Кто может видеть этот пост",
"Who published {number} events": "Которые опубликовали {number} мероприятий",
@ -862,6 +943,7 @@
"You can try another search term or drag and drop the marker on the map": "Вы можете попробовать другие критерии поиска или перетащить маркер на карту",
"You can't change your password because you are registered through {provider}.": "Вы не можете изменить свой пароль, потому что вы зарегистрированы через {provider}.",
"You can't use notifications in this browser.": "Вы не можете использовать уведомления в этом браузере.",
"You can't use push notifications in this browser.": "В этом браузере нельзя использовать push-уведомления.",
"You can't use webpush in this browser.": "Вы не можете использовать webpush в этом браузере.",
"You changed your email or password": "Вы изменили свой адрес электронной почты или пароль",
"You created the discussion {discussion}.": "Вы создали обсуждение {discussion}.",
@ -926,6 +1008,8 @@
"You'll need to change the URLs where there were previously entered.": "Вы должны изменить URL-адреса там, где они были введены ранее.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Вы должны предоставить URL-адрес группы, чтобы другие могли получить доступ к её профилю. Группу нельзя будет найти ни в поиске Mobilizon, ни в обычных поисковых системах.",
"You'll receive a confirmation email.": "Вы получите электронное письмо с подтверждением.",
"YouTube live": "Стрим на YouTube",
"YouTube replay": "Воспроизведение на YouTube",
"Your account has been successfully deleted": "Ваша учетная запись была успешно удалена",
"Your account has been validated": "Ваша учетная запись была подтверждена",
"Your account is being validated": "Ваша учетная запись проверяется",
@ -953,6 +1037,8 @@
"Your timezone was detected as {timezone}.": "Ваш часовой пояс был определен как {timezone}.",
"Your timezone {timezone} isn't supported.": "Ваш часовой пояс {timezone} не поддерживается.",
"Your upcoming events": "Ваши предстоящие мероприятия",
"Zoom in": "Увеличить",
"Zoom out": "Уменьшить",
"[This comment has been deleted by it's author]": "[Этот комментарий был удален автором]",
"[This comment has been deleted]": "[Этот комментарий был удалён]",
"[deleted]": "[удалено]",
@ -966,6 +1052,8 @@
"default Mobilizon privacy policy": "Политика конфиденциальности Mobilizon по умолчанию",
"default Mobilizon terms": "условия использования Mobilizon по умолчанию",
"e.g. 10 Rue Jangot": "например: Садовая 10",
"e.g. Accessibility, Twitch, PeerTube": "например Доступность, Twitch, PeerTube",
"enable the feature": "включить функцию",
"explore the events": "просмотреть мероприятия",
"explore the groups": "посмотреть группы",
"full rules": "полные правила",

View File

@ -3,5 +3,12 @@
"About Mobilizon": "關於Mobilizon",
"About this event": "關於這個活動",
"Accepted": "己接受",
"Add to my calendar": ""
"Add to my calendar": "",
"Back to previous page": "返回上一頁",
"Cancel": "取消",
"Change my email": "修改我的電子郵件地址",
"Change my password": "修改我的密碼",
"Clear": "清除",
"Click to upload": "點擊以上傳",
"Close": "關閉"
}

View File

@ -1,5 +1,3 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from "vue";
import Buefy from "buefy";
import Component from "vue-class-component";

View File

@ -193,13 +193,4 @@ export default class EventMixin extends mixins(Vue) {
console.error(error);
}
}
// eslint-disable-next-line class-methods-use-this
urlToHostname(url: string): string | null {
try {
return new URL(url).hostname;
} catch (e) {
return null;
}
}
}

View File

@ -84,6 +84,14 @@ export default class GroupMixin extends Vue {
]);
}
get isCurrentActorAGroupMember(): boolean {
return this.hasCurrentActorThisRole([
MemberRole.MODERATOR,
MemberRole.ADMINISTRATOR,
MemberRole.MEMBER,
]);
}
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return (

View File

@ -0,0 +1,285 @@
import {
EventMetadataType,
EventMetadataKeyType,
EventMetadataCategories,
} from "@/types/enums";
import { IEventMetadataDescription } from "@/types/event-metadata";
import { i18n } from "@/utils/i18n";
export const eventMetaDataList: IEventMetadataDescription[] = [
{
icon: "wheelchair-accessibility",
key: "mz:accessibility:wheelchairAccessible",
label: i18n.t("Wheelchair accessibility") as string,
description: i18n.t(
"Whether the event is accessible with a wheelchair"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.CHOICE,
choices: {
no: i18n.t("Not accessible with a wheelchair") as string,
partially: i18n.t("Partially accessible with a wheelchair") as string,
fully: i18n.t("Fully accessible with a wheelchair") as string,
},
category: EventMetadataCategories.ACCESSIBILITY,
},
{
icon: "subtitles",
key: "mz:accessibility:live:subtitle",
label: i18n.t("Subtitles") as string,
description: i18n.t("Whether the event live video is subtitled") as string,
value: "",
type: EventMetadataType.BOOLEAN,
keyType: EventMetadataKeyType.PLAIN,
choices: {
true: i18n.t("The event live video contains subtitles") as string,
false: i18n.t(
"The event live video does not contain subtitles"
) as string,
},
category: EventMetadataCategories.ACCESSIBILITY,
},
{
icon: "mz:icon:sign_language",
key: "mz:accessibility:live:sign_language",
label: i18n.t("Sign Language") as string,
description: i18n.t(
"Whether the event is interpreted in sign language"
) as string,
value: "",
type: EventMetadataType.BOOLEAN,
keyType: EventMetadataKeyType.PLAIN,
choices: {
true: i18n.t("The event has a sign language interpreter") as string,
false: i18n.t(
"The event hasn't got a sign language interpreter"
) as string,
},
category: EventMetadataCategories.ACCESSIBILITY,
},
{
icon: "youtube",
key: "mz:replay:youtube:url",
label: i18n.t("YouTube replay") as string,
description: i18n.t(
"The URL where the event live can be watched again after it has ended"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
pattern:
/http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
category: EventMetadataCategories.REPLAY,
},
// {
// icon: "twitch",
// key: "mz:replay:twitch:url",
// label: i18n.t("Twitch replay") as string,
// description: i18n.t(
// "The URL where the event live can be watched again after it has ended"
// ) as string,
// value: "",
// type: EventMetadataType.STRING,
// },
{
icon: "mz:icon:peertube",
key: "mz:replay:peertube:url",
label: i18n.t("PeerTube replay") as string,
description: i18n.t(
"The URL where the event live can be watched again after it has ended"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
category: EventMetadataCategories.REPLAY,
},
{
icon: "mz:icon:peertube",
key: "mz:live:peertube:url",
label: i18n.t("PeerTube live") as string,
description: i18n.t(
"The URL where the event can be watched live"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
category: EventMetadataCategories.LIVE,
},
{
icon: "twitch",
key: "mz:live:twitch:url",
label: i18n.t("Twitch live") as string,
description: i18n.t(
"The URL where the event can be watched live"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
placeholder: "https://www.twitch.tv/",
pattern: /^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/,
category: EventMetadataCategories.LIVE,
},
{
icon: "youtube",
key: "mz:live:youtube:url",
label: i18n.t("YouTube live") as string,
description: i18n.t(
"The URL where the event can be watched live"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
pattern:
/http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
category: EventMetadataCategories.LIVE,
},
{
icon: "calendar-check",
key: "mz:poll:framadate:url",
label: i18n.t("Framadate poll") as string,
description: i18n.t(
"The URL of a poll where the choice for the event date is happening"
) as string,
value: "",
placeholder: "https://framadate.org/",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.TOOLS,
},
{
icon: "file-document-edit",
key: "mz:notes:etherpad:url",
label: i18n.t("Etherpad notes") as string,
description: i18n.t(
"The URL of a pad where notes are being taken collaboratively"
) as string,
value: "",
placeholder: i18n.t(
"https://mensuel.framapad.org/p/some-secret-token"
) as string,
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.TOOLS,
},
{
icon: "twitter",
key: "mz:social:twitter:account",
label: i18n.t("Twitter account") as string,
description: i18n.t(
"A twitter account handle to follow for event updates"
) as string,
value: "",
placeholder: "@JoinMobilizon",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.HANDLE,
category: EventMetadataCategories.SOCIAL,
},
{
icon: "mz:icon:fediverse",
key: "mz:social:fediverse:account_url",
label: i18n.t("Fediverse account") as string,
description: i18n.t(
"A fediverse account URL to follow for event updates"
) as string,
value: "",
placeholder: "https://framapiaf.org/@mobilizon",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.SOCIAL,
},
{
icon: "ticket-confirmation",
key: "mz:ticket:external_url",
label: i18n.t("Online ticketing") as string,
description: i18n.t("An URL to an external ticketing platform") as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.BOOKING,
},
{
icon: "cash",
key: "mz:ticket:price_url",
label: i18n.t("Price sheet") as string,
description: i18n.t(
"A link to a page presenting the price options"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.DETAILS,
},
{
icon: "calendar-text",
key: "mz:schedule_url",
label: i18n.t("Schedule") as string,
description: i18n.t(
"A link to a page presenting the event schedule"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.DETAILS,
},
{
icon: "webcam",
key: "mz:visio:jitsi_meet",
label: i18n.t("Jisti Meet") as string,
description: i18n.t("The Jitsi Meet video teleconference URL") as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.VIDEO_CONFERENCE,
placeholder: "https://meet.jit.si/AFewWords",
},
{
icon: "webcam",
key: "mz:visio:zoom",
label: i18n.t("Zoom") as string,
description: i18n.t("The Zoom video teleconference URL") as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.VIDEO_CONFERENCE,
pattern: /https:\/\/.*\.?zoom.us\/.*/,
},
{
icon: "microsoft-teams",
key: "mz:visio:microsoft_teams",
label: i18n.t("Microsoft Teams") as string,
description: i18n.t(
"The Microsoft Teams video teleconference URL"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.VIDEO_CONFERENCE,
pattern: /https:\/\/teams\.live\.com\/meet\/.+/,
},
{
icon: "google-hangouts",
key: "mz:visio:google_meet",
label: i18n.t("Google Meet") as string,
description: i18n.t("The Google Meet video teleconference URL") as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.VIDEO_CONFERENCE,
pattern: /https:\/\/meet\.google\.com\/.+/,
},
{
icon: "webcam",
key: "mz:visio:big_blue_button",
label: i18n.t("Big Blue Button") as string,
description: i18n.t(
"The Big Blue Button video teleconference URL"
) as string,
value: "",
type: EventMetadataType.STRING,
keyType: EventMetadataKeyType.URL,
category: EventMetadataCategories.VIDEO_CONFERENCE,
},
];

View File

@ -50,7 +50,7 @@ export async function unsubscribeUserToPush(): Promise<string | undefined> {
console.log("performing unsubscribeUserToPush");
const registration = await navigator.serviceWorker.ready;
console.log("found registration", registration);
const subscription = await registration.pushManager.getSubscription();
const subscription = await registration.pushManager?.getSubscription();
console.log("found subscription", subscription);
if (subscription && (await subscription?.unsubscribe()) === true) {
console.log("done unsubscription");

View File

@ -18,6 +18,7 @@ export interface IGroup extends IActor {
todoLists: Paginate<ITodoList>;
discussions: Paginate<IDiscussion>;
organizedEvents: Paginate<IEvent>;
posts: Paginate<IPost>;
physicalAddress: IAddress;
openness: Openness;
visibility: GroupVisibility;

View File

@ -1,10 +0,0 @@
import { ServerParseError } from "@apollo/client/link/http";
import { ServerError } from "@apollo/client/link/utils";
function isServerError(
err: Error | ServerError | ServerParseError | undefined
): err is ServerError {
return !!err && (err as ServerError).statusCode !== undefined;
}
export { isServerError };

View File

@ -251,3 +251,28 @@ export enum SortDirection {
ASC = "ASC",
DESC = "DESC",
}
export enum EventMetadataType {
STRING = "STRING",
INTEGER = "INTEGER",
FLOAT = "FLOAT",
BOOLEAN = "BOOLEAN",
}
export enum EventMetadataKeyType {
PLAIN = "PLAIN",
URL = "URL",
CHOICE = "CHOICE",
HANDLE = "HANDLE",
}
export enum EventMetadataCategories {
ACCESSIBILITY = "ACCESSIBILITY",
LIVE = "LIVE",
REPLAY = "REPLAY",
SOCIAL = "SOCIAL",
TOOLS = "TOOLS",
DETAILS = "DETAILS",
BOOKING = "BOOKING",
VIDEO_CONFERENCE = "VIDEO_CONFERENCE",
}

View File

@ -0,0 +1,23 @@
import {
EventMetadataCategories,
EventMetadataKeyType,
EventMetadataType,
} from "./enums";
export interface IEventMetadata {
key: string;
title?: string;
value: string;
type: EventMetadataType;
}
export interface IEventMetadataDescription extends IEventMetadata {
icon?: string;
placeholder?: string;
description: string;
choices?: Record<string, string>;
keyType: EventMetadataKeyType;
pattern?: RegExp;
label: string;
category: EventMetadataCategories;
}

View File

@ -10,6 +10,7 @@ import type { IParticipant } from "./participant.model";
import { EventOptions } from "./event-options.model";
import type { IEventOptions } from "./event-options.model";
import { EventJoinOptions, EventStatus, EventVisibility } from "./enums";
import { IEventMetadata } from "./event-metadata";
export interface IEventCardOptions {
hideDate: boolean;
@ -49,6 +50,7 @@ interface IEventEditJSON {
tags: string[];
options: IEventOptions;
contacts: { id?: string }[];
metadata: IEventMetadata[];
}
export interface IEvent {
@ -84,6 +86,7 @@ export interface IEvent {
tags: ITag[];
options: IEventOptions;
metadata: IEventMetadata[];
contacts: IActor[];
toEditJSON(): IEventEditJSON;
@ -153,6 +156,8 @@ export class EventModel implements IEvent {
options: IEventOptions = new EventOptions();
metadata: IEventMetadata[] = [];
constructor(hash?: IEvent) {
if (!hash) return;
@ -193,39 +198,53 @@ export class EventModel implements IEvent {
this.contacts = hash.contacts;
this.tags = hash.tags;
this.metadata = hash.metadata;
if (hash.options) this.options = hash.options;
}
toEditJSON(): IEventEditJSON {
return {
id: this.id,
title: this.title,
description: this.description,
beginsOn: this.beginsOn.toISOString(),
endsOn: this.endsOn ? this.endsOn.toISOString() : null,
status: this.status,
visibility: this.visibility,
joinOptions: this.joinOptions,
draft: this.draft,
tags: this.tags.map((t) => t.title),
onlineAddress: this.onlineAddress,
phoneAddress: this.phoneAddress,
physicalAddress: this.removeTypeName(this.physicalAddress),
options: this.removeTypeName(this.options),
attributedToId:
this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null,
contacts: this.contacts.map(({ id }) => ({
id,
})),
};
}
private removeTypeName(entity: any): any {
if (entity?.__typename) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { __typename, ...purgedEntity } = entity;
return purgedEntity;
}
return entity;
return toEditJSON(this);
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function removeTypeName(entity: any): any {
if (entity?.__typename) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { __typename, ...purgedEntity } = entity;
return purgedEntity;
}
return entity;
}
export function toEditJSON(event: IEvent): IEventEditJSON {
return {
id: event.id,
title: event.title,
description: event.description,
beginsOn: event.beginsOn.toISOString(),
endsOn: event.endsOn ? event.endsOn.toISOString() : null,
status: event.status,
visibility: event.visibility,
joinOptions: event.joinOptions,
draft: event.draft,
tags: event.tags.map((t) => t.title),
onlineAddress: event.onlineAddress,
phoneAddress: event.phoneAddress,
physicalAddress: removeTypeName(event.physicalAddress),
options: removeTypeName(event.options),
metadata: event.metadata.map(({ key, value, type, title }) => ({
key,
value,
type,
title,
})),
attributedToId:
event.attributedTo && event.attributedTo.id
? event.attributedTo.id
: null,
contacts: event.contacts.map(({ id }) => ({
id,
})),
};
}

View File

@ -95,14 +95,17 @@ export async function initializeCurrentActor(
}
export async function logout(
apollo: ApolloClient<NormalizedCacheObject>
apollo: ApolloClient<NormalizedCacheObject>,
performServerLogout = true
): Promise<void> {
await apollo.mutate({
mutation: LOGOUT,
variables: {
refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN),
},
});
if (performServerLogout) {
await apollo.mutate({
mutation: LOGOUT,
variables: {
refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN),
},
});
}
await apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,

View File

@ -8,8 +8,18 @@ import pluralizationRules from "../i18n/pluralRules";
const DEFAULT_LOCALE = "en_US";
const localeInLocalStorage = getLocaleData();
console.debug("localeInLocalStorage", localeInLocalStorage);
let language =
getLocaleData() || (document.documentElement.getAttribute("lang") as string);
localeInLocalStorage ||
(document.documentElement.getAttribute("lang") as string);
console.debug(
"localeInLocalStorage or fallback to lang html attribute",
language
);
language =
language ||
@ -18,11 +28,15 @@ language =
"_"
);
console.debug("language or fallback to window.navigator language", language);
export const locale =
language && Object.prototype.hasOwnProperty.call(langs, language)
? language
: language.split("-")[0];
console.debug("chosen locale", locale);
Vue.use(VueI18n);
export const i18n = new VueI18n({
@ -35,13 +49,26 @@ export const i18n = new VueI18n({
pluralizationRules,
});
console.debug("set VueI18n with default locale", DEFAULT_LOCALE);
const loadedLanguages = [DEFAULT_LOCALE];
function setI18nLanguage(lang: string): string {
console.debug("setting i18n locale to", lang);
i18n.locale = lang;
setLanguageInDOM(lang);
return lang;
}
function setLanguageInDOM(lang: string): void {
const fixedLang = lang.replace(/_/g, "-");
const html = document.documentElement;
const documentLang = html.getAttribute("lang");
if (documentLang !== fixedLang) {
html.setAttribute("lang", fixedLang);
}
}
function fileForLanguage(matches: Record<string, string>, lang: string) {
if (Object.prototype.hasOwnProperty.call(matches, lang)) {
return matches[lang];
@ -70,14 +97,17 @@ Vue.use(DateFnsPlugin, { locale: dateFnsfileForLanguage(locale) });
export async function loadLanguageAsync(lang: string): Promise<string> {
// If the same language
if (i18n.locale === lang) {
console.debug("already using language", lang);
return Promise.resolve(setI18nLanguage(lang));
}
// If the language was already loaded
if (loadedLanguages.includes(lang)) {
console.debug("language already loaded", lang);
return Promise.resolve(setI18nLanguage(lang));
}
// If the language hasn't been loaded yet
console.debug("loading language", lang);
const newMessages = await import(
/* webpackChunkName: "lang-[request]" */ `@/i18n/${vueI18NfileForLanguage(
lang
@ -88,7 +118,9 @@ export async function loadLanguageAsync(lang: string): Promise<string> {
return setI18nLanguage(lang);
}
console.debug("loading async locale", locale);
loadLanguageAsync(locale);
console.debug("loaded async locale", locale);
export function formatList(list: string[]): string {
if (window.Intl && Intl.ListFormat) {

View File

@ -129,6 +129,7 @@ import RouteName from "../../router/name";
import { changeIdentity } from "../../utils/auth";
import identityEditionMixin from "../../mixins/identityEdition";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import { ActorType } from "@/types/enums";
@Component({
apollo: {
@ -180,8 +181,18 @@ export default class Register extends mixins(identityEditionMixin) {
});
if (identitiesData && localData) {
identitiesData.identities.push(localData.registerPerson);
store.writeQuery({ query: IDENTITIES, data: identitiesData });
const newPersonData = {
...localData.registerPerson,
type: ActorType.PERSON,
};
store.writeQuery({
query: IDENTITIES,
data: {
...identitiesData,
identities: [...identitiesData.identities, newPersonData],
},
});
}
}
},

View File

@ -218,6 +218,7 @@ import {
DELETE_PERSON,
FETCH_PERSON,
IDENTITIES,
PERSON_FRAGMENT,
UPDATE_PERSON,
} from "../../../graphql/actor";
import { IPerson, Person } from "../../../types/actor";
@ -236,6 +237,8 @@ import { IConfig } from "@/types/config.model";
import { CONFIG } from "@/graphql/config";
import { ServerParseError } from "@apollo/client/link/http";
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
import pick from "lodash/pick";
import { ActorType } from "@/types/enums";
@Component({
components: {
@ -345,11 +348,14 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
});
if (data) {
data.identities = data.identities.filter(
(i) => i.id !== this.identity.id
);
store.writeQuery({ query: IDENTITIES, data });
store.writeQuery({
query: IDENTITIES,
data: {
identities: data.identities.filter(
(i) => i.id !== this.identity.id
),
},
});
}
},
});
@ -392,14 +398,16 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
});
if (data && updateData?.updatePerson) {
const index = data.identities.findIndex(
(i) => i.id === this.identity.id
);
this.$set(data.identities, index, updateData?.updatePerson);
this.maybeUpdateCurrentActorCache(updateData?.updatePerson);
store.writeQuery({ query: IDENTITIES, data });
store.writeFragment({
fragment: PERSON_FRAGMENT,
id: `Person:${updateData?.updatePerson.id}`,
data: {
...updateData?.updatePerson,
type: ActorType.PERSON,
},
});
}
},
});
@ -430,9 +438,15 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
});
if (data && updateData?.createPerson) {
data.identities.push(updateData?.createPerson);
store.writeQuery({ query: IDENTITIES, data });
store.writeQuery({
query: IDENTITIES,
data: {
identities: [
...data.identities,
{ ...updateData?.createPerson, type: ActorType.PERSON },
],
},
});
}
},
});
@ -484,15 +498,15 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
async generateFeedTokens(): Promise<void> {
const newToken = await this.createNewFeedToken();
this.identity.feedTokens.push(newToken);
this.identity.feedTokens = [...this.identity.feedTokens, newToken];
}
async regenerateFeedTokens(): Promise<void> {
if (this.identity?.feedTokens.length < 1) return;
await this.deleteFeedToken(this.identity.feedTokens[0].token);
const newToken = await this.createNewFeedToken();
this.identity.feedTokens.pop();
this.identity.feedTokens.push(newToken);
const feedTokens = this.identity.feedTokens.slice(0, -1);
this.identity.feedTokens = [...feedTokens, newToken];
}
private async deleteFeedToken(token: string): Promise<void> {
@ -582,7 +596,7 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
}
}
private async buildVariables() {
private async buildVariables(): Promise<Record<string, unknown>> {
/**
* We set the avatar only if user has selected one
*/
@ -594,8 +608,13 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
`${this.identity.preferredUsername}'s avatar`
);
}
const res = { ...this.identity, ...avatarObj };
return res;
return pick({ ...this.identity, ...avatarObj }, [
"id",
"preferredUsername",
"name",
"summary",
"avatar",
]);
}
private async redirectIfNoIdentitySelected(identityParam?: string) {

View File

@ -1,6 +1,6 @@
<template>
<section>
<div class="container" v-if="isCurrentActorOrganizer">
<div class="container" v-if="hasCurrentActorPermissionsToEdit">
<h1 class="title" v-if="isUpdate === true">
{{ $t("Update event {name}", { name: event.title }) }}
</h1>
@ -122,6 +122,15 @@
</span>
</p>
</div>
<subtitle>{{ $t("Event metadata") }}</subtitle>
<p>
{{
$t(
"Integrate this event with 3rd-party tools and show metadata for the event."
)
}}
</p>
<event-metadata-list v-model="event.metadata" />
<subtitle>{{ $t("Who can view this event and participate") }}</subtitle>
<div class="field">
<b-radio
@ -152,7 +161,7 @@
v-if="config && config.anonymous.participation.allowed"
>
<label class="label">{{ $t("Anonymous participations") }}</label>
<b-switch v-model="event.options.anonymousParticipation">
<b-switch v-model="eventOptions.anonymousParticipation">
{{
$t("I want to allow people to participate without an account.")
}}
@ -191,18 +200,18 @@
<b-numberinput
controls-position="compact"
min="1"
v-model="event.options.maximumAttendeeCapacity"
v-model="eventOptions.maximumAttendeeCapacity"
/>
</b-field>
<!--
<b-field>
<b-switch v-model="event.options.showRemainingAttendeeCapacity">
<b-switch v-model="eventOptions.showRemainingAttendeeCapacity">
{{ $t('Show remaining number of places') }}
</b-switch>
</b-field>
<b-field>
<b-switch v-model="event.options.showParticipationPrice">
<b-switch v-model="eventOptions.showParticipationPrice">
{{ $t('Display participation price') }}
</b-switch>
</b-field>-->
@ -212,7 +221,7 @@
<div class="field">
<b-radio
v-model="event.options.commentModeration"
v-model="eventOptions.commentModeration"
name="commentModeration"
:native-value="CommentModeration.ALLOW_ALL"
>{{ $t("Allow all comments from users with accounts") }}</b-radio
@ -220,7 +229,7 @@
</div>
<!-- <div class="field">-->
<!-- <b-radio v-model="event.options.commentModeration"-->
<!-- <b-radio v-model="eventOptions.commentModeration"-->
<!-- name="commentModeration"-->
<!-- :native-value="CommentModeration.MODERATED">-->
<!-- {{ $t('Moderated comments (shown after approval)') }}-->
@ -229,7 +238,7 @@
<div class="field">
<b-radio
v-model="event.options.commentModeration"
v-model="eventOptions.commentModeration"
name="commentModeration"
:native-value="CommentModeration.CLOSED"
>{{ $t("Close comments for all (except for admins)") }}</b-radio
@ -269,6 +278,11 @@
</b-field>
</form>
</div>
<div class="container section" v-else>
<b-message type="is-danger">
{{ $t("Only group moderators can create, edit and delete events.") }}
</b-message>
</div>
<b-modal v-model="dateSettingsIsOpen" has-modal-card trap-focus>
<form action>
<div class="modal-card" style="width: auto">
@ -277,12 +291,12 @@
</header>
<section class="modal-card-body">
<b-field :label="$t('Event page settings')">
<b-switch v-model="event.options.showStartTime">{{
<b-switch v-model="eventOptions.showStartTime">{{
$t("Show the time when the event begins")
}}</b-switch>
</b-field>
<b-field>
<b-switch v-model="event.options.showEndTime">{{
<b-switch v-model="eventOptions.showEndTime">{{
$t("Show the time when the event ends")
}}</b-switch>
</b-field>
@ -305,7 +319,7 @@
aria-label="main navigation"
class="navbar save__navbar"
:class="{ 'is-fixed-bottom': showFixedNavbar }"
v-if="isCurrentActorOrganizer"
v-if="hasCurrentActorPermissionsToEdit"
>
<div class="container">
<div class="navbar-menu">
@ -441,14 +455,14 @@ section {
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { RefetchQueryDescription } from "@apollo/client/core/watchQueryOptions";
import PictureUpload from "@/components/PictureUpload.vue";
import EditorComponent from "@/components/Editor.vue";
import TagInput from "@/components/Event/TagInput.vue";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import EventMetadataList from "@/components/Event/EventMetadataList.vue";
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
import Subtitle from "@/components/Utils/Subtitle.vue";
import { Route } from "vue-router";
import { RawLocation, Route } from "vue-router";
import { formatList } from "@/utils/i18n";
import {
ActorType,
@ -456,6 +470,7 @@ import {
EventJoinOptions,
EventStatus,
EventVisibility,
MemberRole,
ParticipantRole,
} from "@/types/enums";
import OrganizerPickerWrapper from "../../components/Event/OrganizerPickerWrapper.vue";
@ -465,14 +480,26 @@ import {
EVENT_PERSON_PARTICIPATION,
FETCH_EVENT,
} from "../../graphql/event";
import { EventModel, IEvent } from "../../types/event.model";
import {
EventModel,
IEvent,
removeTypeName,
toEditJSON,
} from "../../types/event.model";
import {
CURRENT_ACTOR_CLIENT,
IDENTITIES,
LOGGED_USER_DRAFTS,
LOGGED_USER_PARTICIPATIONS,
PERSON_MEMBERSHIP_GROUP,
} from "../../graphql/actor";
import { displayNameAndUsername, IActor, IGroup } from "../../types/actor";
import {
displayNameAndUsername,
IActor,
IGroup,
IPerson,
usernameWithDomain,
} from "../../types/actor";
import { TAGS } from "../../graphql/tags";
import { ITag } from "../../types/tag.model";
import {
@ -484,8 +511,13 @@ import RouteName from "../../router/name";
import "intersection-observer";
import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import {
ApolloCache,
FetchResult,
InternalRefetchQueriesInclude,
} from "@apollo/client/core";
import cloneDeep from "lodash/cloneDeep";
import { IEventOptions } from "@/types/event-options.model";
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
@ -498,6 +530,7 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
TagInput,
PictureUpload,
Editor: EditorComponent,
EventMetadataList,
},
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
@ -518,6 +551,22 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
return !this.eventId;
},
},
person: {
query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
id: this.currentActor.id,
group: usernameWithDomain(this.event?.attributedTo),
};
},
skip() {
return (
!this.event?.attributedTo ||
!this.event?.attributedTo?.preferredUsername
);
},
},
},
metaInfo() {
return {
@ -542,8 +591,12 @@ export default class EditEvent extends Vue {
event: IEvent = new EventModel();
unmodifiedEvent: IEvent = new EventModel();
identities: IActor[] = [];
person!: IPerson;
config!: IConfig;
pictureFile: File | null = null;
@ -552,8 +605,6 @@ export default class EditEvent extends Vue {
EventVisibility = EventVisibility;
needsApproval = false;
canPromote = true;
limitedPlaces = false;
@ -622,6 +673,14 @@ export default class EditEvent extends Vue {
return this.event.attributedTo?.id !== undefined;
}
get eventOptions(): IEventOptions {
return removeTypeName(cloneDeep(this.event.options));
}
set eventOptions(options: IEventOptions) {
this.event.options = options;
}
async mounted(): Promise<void> {
this.observer = new IntersectionObserver(
(entries) => {
@ -639,16 +698,17 @@ export default class EditEvent extends Vue {
this.observer.observe(this.$refs.bottomObserver as Element);
this.pictureFile = await buildFileFromIMedia(this.event.picture);
this.limitedPlaces = this.event.options.maximumAttendeeCapacity > 0;
this.limitedPlaces = this.eventOptions.maximumAttendeeCapacity > 0;
if (!(this.isUpdate || this.isDuplicate)) {
this.initializeEvent();
} else {
this.event = {
this.event = new EventModel({
...this.event,
options: cloneDeep(this.event.options),
description: this.event.description || "",
};
});
}
this.unmodifiedEvent = cloneDeep(this.event);
}
createOrUpdateDraft(e: Event): void {
@ -748,19 +808,29 @@ export default class EditEvent extends Vue {
}
}
get isCurrentActorOrganizer(): boolean {
get hasCurrentActorPermissionsToEdit(): boolean {
return !(
this.eventId &&
this.event.organizerActor?.id !== undefined &&
!this.identities
.map(({ id }) => id)
.includes(this.event.organizerActor?.id)
.includes(this.event.organizerActor?.id) &&
!this.hasGroupPrivileges
);
}
get hasGroupPrivileges(): boolean {
return (
this.person?.memberships?.total > 0 &&
[MemberRole.MODERATOR, MemberRole.ADMINISTRATOR].includes(
this.person?.memberships?.elements[0].role
)
);
}
get updateEventMessage(): string {
// if (this.unmodifiedEvent.draft && !this.event.draft)
// return this.$i18n.t("The event has been updated and published") as string;
if (this.unmodifiedEvent.draft && !this.event.draft)
return this.$i18n.t("The event has been updated and published") as string;
return (
this.event.draft
? this.$i18n.t("The draft event has been updated")
@ -825,7 +895,9 @@ export default class EditEvent extends Vue {
* Refresh drafts or participation cache depending if the event is still draft or not
*/
// eslint-disable-next-line class-methods-use-this
private postRefetchQueries(updateEvent: IEvent): RefetchQueryDescription {
private postRefetchQueries(
updateEvent: IEvent
): InternalRefetchQueriesInclude {
if (updateEvent.draft) {
return [
{
@ -854,7 +926,10 @@ export default class EditEvent extends Vue {
* Build variables for Event GraphQL creation query
*/
private async buildVariables() {
let res = new EventModel(this.event).toEditJSON();
let res = {
...toEditJSON(new EventModel(this.event)),
options: this.eventOptions,
};
const organizerActor = this.event.organizerActor?.id
? this.event.organizerActor
: this.organizerActor;
@ -897,19 +972,22 @@ export default class EditEvent extends Vue {
@Watch("limitedPlaces")
updatedEventCapacityOptions(limitedPlaces: boolean): void {
if (!limitedPlaces) {
this.event.options.maximumAttendeeCapacity = 0;
this.event.options.remainingAttendeeCapacity = 0;
this.event.options.showRemainingAttendeeCapacity = false;
this.eventOptions.maximumAttendeeCapacity = 0;
this.eventOptions.remainingAttendeeCapacity = 0;
this.eventOptions.showRemainingAttendeeCapacity = false;
} else {
this.event.options.maximumAttendeeCapacity =
this.event.options.maximumAttendeeCapacity ||
this.eventOptions.maximumAttendeeCapacity =
this.eventOptions.maximumAttendeeCapacity ||
DEFAULT_LIMIT_NUMBER_OF_PLACES;
}
}
@Watch("needsApproval")
updateEventJoinOptions(needsApproval: boolean): void {
if (needsApproval === true) {
get needsApproval(): boolean {
return this.event?.joinOptions == EventJoinOptions.RESTRICTED;
}
set needsApproval(value: boolean) {
if (value === true) {
this.event.joinOptions = EventJoinOptions.RESTRICTED;
} else {
this.event.joinOptions = EventJoinOptions.FREE;
@ -925,10 +1003,12 @@ export default class EditEvent extends Vue {
/**
* Confirm cancel
*/
confirmGoElsewhere(callback: () => any): void {
if (!this.isEventModified) {
callback();
}
confirmGoElsewhere(): Promise<boolean> {
// TODO: Make calculation of changes work again and bring this back
// If the event wasn't modified, no need to warn
// if (!this.isEventModified) {
// return Promise.resolve(true);
// }
const title: string = this.isUpdate
? (this.$t("Cancel edition") as string)
: (this.$t("Cancel creation") as string);
@ -942,14 +1022,17 @@ export default class EditEvent extends Vue {
{ title: this.event.title }
) as string);
this.$buefy.dialog.confirm({
title,
message,
confirmText: this.$t("Abandon editing") as string,
cancelText: this.$t("Continue editing") as string,
type: "is-warning",
hasIcon: true,
onConfirm: callback,
return new Promise((resolve) => {
this.$buefy.dialog.confirm({
title,
message,
confirmText: this.$t("Abandon editing") as string,
cancelText: this.$t("Continue editing") as string,
type: "is-warning",
hasIcon: true,
onConfirm: () => resolve(true),
onCancel: () => resolve(false),
});
});
}
@ -957,21 +1040,29 @@ export default class EditEvent extends Vue {
* Confirm cancel
*/
confirmGoBack(): void {
this.confirmGoElsewhere(() => this.$router.go(-1));
this.$router.go(-1);
}
// eslint-disable-next-line consistent-return
beforeRouteLeave(to: Route, from: Route, next: () => void): void {
async beforeRouteLeave(
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: any) => void)) => void
): Promise<void> {
if (to.name === RouteName.EVENT) return next();
this.confirmGoElsewhere(() => next());
if (await this.confirmGoElsewhere()) {
return next();
}
return next(false);
}
get isEventModified(): boolean {
// return (
// JSON.stringify(this.event.toEditJSON()) !==
// JSON.stringify(this.unmodifiedEvent)
// );
return false;
return (
this.event &&
this.unmodifiedEvent &&
JSON.stringify(toEditJSON(this.event)) !==
JSON.stringify(this.unmodifiedEvent)
);
}
get beginsOn(): Date {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<template>
<div class="container is-widescreen">
<div class="header">
<nav class="breadcrumb" aria-label="breadcrumbs">
<nav class="breadcrumb" :aria-label="$t('Breadcrumbs')">
<ul>
<li>
<router-link :to="{ name: RouteName.MY_GROUPS }">{{
@ -10,6 +10,7 @@
</li>
<li class="is-active">
<router-link
aria-current-value="location"
v-if="group && group.preferredUsername"
:to="{
name: RouteName.GROUP,
@ -21,6 +22,7 @@
</li>
</ul>
</nav>
<b-loading :active.sync="$apollo.loading"></b-loading>
<invitations
v-if="isCurrentActorAnInvitedGroupMember"
:invitations="[groupMember]"
@ -172,7 +174,6 @@
</b-button>
<b-dropdown
class="menu-dropdown"
v-if="isCurrentActorAGroupMember || previewPublic"
position="is-bottom-left"
aria-role="menu"
>
@ -181,15 +182,18 @@
outlined
role="button"
icon-left="dots-horizontal"
aria-label="Other actions"
:aria-label="$t('Other actions')"
/>
<b-dropdown-item aria-role="menuitem">
<b-dropdown-item
aria-role="menuitem"
v-if="isCurrentActorAGroupMember || previewPublic"
>
<b-switch v-model="previewPublic">{{
$t("Public preview")
}}</b-switch>
</b-dropdown-item>
<b-dropdown-item
v-if="!previewPublic"
v-if="!previewPublic && isCurrentActorAGroupMember"
aria-role="menuitem"
@click="triggerShare()"
>
@ -400,7 +404,7 @@
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Post a public message") }}</router-link
>{{ $t("+ Create a post") }}</router-link
>
</template>
</group-section>
@ -470,10 +474,10 @@
<subtitle>{{ $t("Upcoming events") }}</subtitle>
<div
class="organized-events-wrapper"
v-if="group && group.organizedEvents.total > 0"
v-if="group && organizedEvents.elements.length > 0"
>
<EventMinimalistCard
v-for="event in group.organizedEvents.elements"
v-for="event in organizedEvents.elements"
:event="event"
:key="event.uuid"
class="organized-event"
@ -484,20 +488,20 @@
</empty-content>
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
<router-link
v-if="group.organizedEvents.total > 0"
v-if="organizedEvents.total > 0"
:to="{
name: RouteName.GROUP_EVENTS,
params: { preferredUsername: usernameWithDomain(group) },
query: { future: group.organizedEvents.elements.length > 0 },
query: { future: organizedEvents.elements.length > 0 },
}"
>{{ $t("View all events") }}</router-link
>
</section>
<section>
<subtitle>{{ $t("Latest posts") }}</subtitle>
<div v-if="group.posts.total > 0" class="posts-wrapper">
<div v-if="posts.elements.length > 0" class="posts-wrapper">
<post-list-item
v-for="post in group.posts.elements"
v-for="post in posts.elements"
:key="post.id"
:post="post"
/>
@ -507,7 +511,7 @@
</empty-content>
<b-skeleton animated v-else-if="$apollo.loading"></b-skeleton>
<router-link
v-if="group.posts.total > 0"
v-if="posts.total > 0"
:to="{
name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) },
@ -576,7 +580,7 @@ import { IConfig } from "@/types/config.model";
import GroupMixin from "@/mixins/group";
import { mixins } from "vue-class-component";
import { JOIN_GROUP } from "@/graphql/member";
import { MemberRole, Openness } from "@/types/enums";
import { MemberRole, Openness, PostVisibility } from "@/types/enums";
import { IMember } from "@/types/actor/member.model";
import RouteName from "../../router/name";
import GroupSection from "../../components/Group/GroupSection.vue";
@ -586,6 +590,9 @@ import { LEAVE_GROUP } from "@/graphql/group";
import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue";
import EventMetadataBlock from "../../components/Event/EventMetadataBlock.vue";
import EmptyContent from "../../components/Utils/EmptyContent.vue";
import { Paginate } from "@/types/paginate";
import { IEvent } from "@/types/event.model";
import { IPost } from "@/types/post.model";
@Component({
apollo: {
@ -793,31 +800,11 @@ export default class Group extends mixins(GroupMixin) {
return undefined;
}
get groupMemberships(): (string | undefined)[] {
return this.person?.memberships?.elements
.filter(
(membership: IMember) =>
![
MemberRole.REJECTED,
MemberRole.NOT_APPROVED,
MemberRole.INVITED,
].includes(membership.role)
)
.map(({ parent: { id } }) => id);
}
@Watch("isCurrentActorAGroupMember")
refetchGroupData(): void {
this.$apollo.queries.group.refetch();
}
get isCurrentActorAGroupMember(): boolean {
return (
this.groupMemberships !== undefined &&
this.groupMemberships.includes(this.group.id)
);
}
get isCurrentActorARejectedGroupMember(): boolean {
return (
this.person &&
@ -877,6 +864,30 @@ export default class Group extends mixins(GroupMixin) {
this.config.anonymous.reports.allowed)
);
}
get organizedEvents(): Paginate<IEvent> {
return {
total: this.group.organizedEvents.total,
elements: this.group.organizedEvents.elements.filter((event: IEvent) => {
if (this.previewPublic) {
return !event.draft; // TODO when events get visibility access add visibility constraint like below for posts
}
return true;
}),
};
}
get posts(): Paginate<IPost> {
return {
total: this.group.posts.total,
elements: this.group.posts.elements.filter((post: IPost) => {
if (this.previewPublic) {
return !(post.draft || post.visibility == PostVisibility.PRIVATE);
}
return true;
}),
};
}
}
</script>
<style lang="scss" scoped>

View File

@ -70,14 +70,21 @@
<b-loading :active.sync="$apollo.loading" />
</p>
<b-loading :active.sync="$apollo.loading" />
<div v-if="this.events.total > 0" class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in this.events.elements.slice(0, 6)"
:key="event.uuid"
>
<EventCard :event="event" />
<div v-if="this.events.total > 0">
<div class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in this.events.elements.slice(0, 6)"
:key="event.uuid"
>
<EventCard :event="event" />
</div>
</div>
<span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link
>
</span>
</div>
<b-message v-else type="is-danger">{{
$t("No events found")
@ -316,14 +323,21 @@
<b-loading :active.sync="$apollo.loading" />
</p>
<div v-if="this.events.total > 0" class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in this.events.elements.slice(0, 6)"
:key="event.uuid"
>
<recent-event-card-wrapper :event="event" />
<div v-if="this.events.total > 0">
<div class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in this.events.elements.slice(0, 6)"
:key="event.uuid"
>
<recent-event-card-wrapper :event="event" />
</div>
</div>
<span class="view-all">
<router-link :to="{ name: RouteName.SEARCH }"
>{{ $t("View everything") }} >></router-link
>
</span>
</div>
<b-message v-else type="is-danger"
>{{ $t("No events found") }}<br />
@ -380,7 +394,9 @@ import Subtitle from "../components/Utils/Subtitle.vue";
currentUser: CURRENT_USER_CLIENT,
loggedUser: {
query: USER_SETTINGS,
fetchPolicy: "no-cache",
skip() {
return !this.currentUser || this.currentUser.isLoggedIn === false;
},
error() {
return null;
},
@ -641,7 +657,7 @@ main > div > .container {
}
.columns {
margin: 1rem auto 3rem;
margin: 1rem auto 0;
}
}
@ -726,13 +742,6 @@ section.hero {
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
0deg,
$white 0,
rgba(0, 0, 0, 0) 5%,
rgba(0, 0, 0, 0) 90%,
$white 100%
);
z-index: 1;
}

View File

@ -38,7 +38,7 @@
}}
</p>
<!-- The following should just be replaced with the SearchField component but it fails for some reason -->
<form @submit="enter">
<form @submit.prevent="enter">
<b-field class="search">
<b-input
expanded

View File

@ -207,7 +207,7 @@ import { FETCH_GROUP } from "@/graphql/group";
variables() {
return {
id: this.currentActor.id,
group: this.actualGroup.preferredUsername,
group: usernameWithDomain(this.actualGroup),
};
},
skip() {

View File

@ -45,7 +45,7 @@
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Post a public message") }}</router-link
>{{ $t("+ Create a post") }}</router-link
>
</div>
<div class="post-list">

View File

@ -1,5 +1,5 @@
<template>
<article class="container" v-if="post">
<article class="container post" v-if="post">
<header>
<div class="banner-container">
<lazy-image-wrapper :picture="post.picture" />
@ -24,6 +24,29 @@
<b-icon icon="clock" size="is-small" />
{{ post.publishAt | formatDateTimeString }}
</span>
<span
class="published has-text-grey-dark"
:title="
$options.filters.formatDateTimeString(
post.updatedAt,
true,
'short'
)
"
v-else
>
<b-icon icon="clock" size="is-small" />
{{
$t("Edited {relative_time} ago", {
relative_time: formatDistanceToNowStrict(
new Date(post.updatedAt),
{
locale: $dateFnsLocale,
}
),
})
}}
</span>
<span
v-if="post.visibility === PostVisibility.PRIVATE"
class="has-text-grey-dark"
@ -55,6 +78,24 @@
</div>
</div>
</header>
<b-message
:title="$t('Members-only post')"
class="mx-4"
type="is-warning"
:closable="false"
v-if="
!$apollo.loading &&
isInstanceModerator &&
!isCurrentActorAGroupMember &&
post.visibility === PostVisibility.PRIVATE
"
>
{{
$t(
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator."
)
}}
</b-message>
<section v-html="post.body" class="content" />
<section class="tags">
@ -73,9 +114,13 @@
import { Component, Prop } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group";
import { PostVisibility } from "@/types/enums";
import { ICurrentUserRole, PostVisibility } from "@/types/enums";
import { IMember } from "@/types/actor/member.model";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import {
CURRENT_ACTOR_CLIENT,
PERSON_MEMBERSHIPS,
PERSON_MEMBERSHIP_GROUP,
} from "../../graphql/actor";
import { FETCH_POST } from "../../graphql/post";
import { IPost } from "../../types/post.model";
import { usernameWithDomain } from "../../types/actor";
@ -83,9 +128,13 @@ import RouteName from "../../router/name";
import Tag from "../../components/Tag.vue";
import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue";
import ActorInline from "../../components/Account/ActorInline.vue";
import { formatDistanceToNowStrict } from "date-fns";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
@Component({
apollo: {
currentUser: CURRENT_USER_CLIENT,
currentActor: CURRENT_ACTOR_CLIENT,
memberships: {
query: PERSON_MEMBERSHIPS,
@ -115,6 +164,23 @@ import ActorInline from "../../components/Account/ActorInline.vue";
this.handleErrors(graphQLErrors);
},
},
person: {
query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
id: this.currentActor.id,
group: usernameWithDomain(this.post.attributedTo),
};
},
skip() {
return (
!this.currentActor ||
!this.currentActor.id ||
!this.post?.attributedTo
);
},
},
},
components: {
Tag,
@ -142,8 +208,12 @@ export default class Post extends mixins(GroupMixin) {
RouteName = RouteName;
currentUser!: ICurrentUser;
usernameWithDomain = usernameWithDomain;
formatDistanceToNowStrict = formatDistanceToNowStrict;
PostVisibility = PostVisibility;
handleErrors(errors: any[]): void {
@ -158,10 +228,17 @@ export default class Post extends mixins(GroupMixin) {
.map(({ parent: { id } }) => id)
.includes(this.post.attributedTo.id);
}
get isInstanceModerator(): boolean {
return [
ICurrentUserRole.ADMINISTRATOR,
ICurrentUserRole.MODERATOR,
].includes(this.currentUser.role);
}
}
</script>
<style lang="scss" scoped>
article {
article.post {
background: $white !important;
header {
display: flex;

View File

@ -244,7 +244,6 @@ import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
import ResourceItem from "@/components/Resource/ResourceItem.vue";
import FolderItem from "@/components/Resource/FolderItem.vue";
import Draggable from "vuedraggable";
import { RefetchQueryDescription } from "@apollo/client/core/watchQueryOptions";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { IActor, usernameWithDomain } from "../../types/actor";
import RouteName from "../../router/name";
@ -264,7 +263,11 @@ import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import ResourceMixin from "../../mixins/resource";
import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import {
ApolloCache,
FetchResult,
InternalRefetchQueriesInclude,
} from "@apollo/client/core";
import VueRouter from "vue-router";
const { isNavigationFailure, NavigationFailureType } = VueRouter;
@ -521,7 +524,7 @@ export default class Resources extends Mixins(ResourceMixin) {
}
// eslint-disable-next-line class-methods-use-this
private postRefreshQueries(): RefetchQueryDescription {
private postRefreshQueries(): InternalRefetchQueriesInclude {
return [
{
query: GET_RESOURCE,

View File

@ -69,13 +69,27 @@
>
<b-loading :active.sync="$apollo.loading"></b-loading>
<h2 class="title">{{ $t("Featured events") }}</h2>
<div v-if="events.elements.length > 0" class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in events.elements"
:key="event.uuid"
>
<EventCard :event="event" />
<div v-if="events.elements.length > 0">
<div class="columns is-multiline">
<div
class="column is-one-third-desktop"
v-for="event in events.elements"
:key="event.uuid"
>
<EventCard :event="event" />
</div>
</div>
<div class="pagination" v-if="events.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="events.total"
v-model="eventPage"
:per-page="EVENT_PAGE_LIMIT"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
>
</b-pagination>
</div>
</div>
<b-message
@ -85,6 +99,7 @@
>
</section>
<b-tabs v-else v-model="activeTab" type="is-boxed" class="searchTabs">
<b-loading :active.sync="$apollo.loading"></b-loading>
<b-tab-item>
<template slot="header">
<b-icon icon="calendar"></b-icon>
@ -103,7 +118,7 @@
<EventCard :event="event" />
</div>
</div>
<div class="pagination">
<div class="pagination" v-if="searchEvents.total > EVENT_PAGE_LIMIT">
<b-pagination
:total="searchEvents.total"
v-model="eventPage"
@ -119,12 +134,6 @@
<b-message v-else-if="$apollo.loading === false" type="is-danger">{{
$t("No events found")
}}</b-message>
<b-loading
v-else-if="$apollo.loading"
:is-full-page="false"
v-model="$apollo.loading"
:can-cancel="false"
/>
</b-tab-item>
<b-tab-item v-if="!tag">
<template slot="header">
@ -162,12 +171,6 @@
<b-message v-else-if="$apollo.loading === false" type="is-danger">
{{ $t("No groups found") }}
</b-message>
<b-loading
v-else-if="$apollo.loading"
:is-full-page="false"
v-model="$apollo.loading"
:can-cancel="false"
/>
</b-tab-item>
</b-tabs>
</div>
@ -209,9 +212,9 @@ interface ISearchTimeOption {
end?: Date | null;
}
const EVENT_PAGE_LIMIT = 10;
const EVENT_PAGE_LIMIT = 12;
const GROUP_PAGE_LIMIT = 10;
const GROUP_PAGE_LIMIT = 12;
const DEFAULT_RADIUS = 25; // value to set if radius is null but location set
@ -229,7 +232,15 @@ const THROTTLE = 2000; // minimum interval in ms between two requests
},
apollo: {
config: CONFIG,
events: FETCH_EVENTS,
events: {
query: FETCH_EVENTS,
variables() {
return {
page: this.eventPage,
limit: EVENT_PAGE_LIMIT,
};
},
},
searchEvents: {
query: SEARCH_EVENTS,
fetchPolicy: "cache-and-network",
@ -290,8 +301,6 @@ export default class Search extends Vue {
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
eventPage = 1;
groupPage = 1;
location: IAddress = new Address();
@ -366,6 +375,17 @@ export default class Search extends Vue {
this.$apollo.queries.searchEvents.refetch();
}
get eventPage(): number {
return parseInt(this.$route.query.eventPage as string, 10) || 1;
}
set eventPage(page: number) {
this.$router.push({
name: RouteName.SEARCH,
query: { ...this.$route.query, eventPage: page.toString() },
});
}
get search(): string | undefined {
return this.$route.query.term as string;
}

View File

@ -686,10 +686,15 @@ export default class Notifications extends Vue {
}
async checkCanShowWebPush(): Promise<boolean> {
if (!window.isSecureContext || !("serviceWorker" in navigator))
try {
if (!window.isSecureContext || !("serviceWorker" in navigator))
return Promise.resolve(false);
const registration = await navigator.serviceWorker.getRegistration();
return registration !== undefined;
} catch (e) {
console.error(e);
return Promise.resolve(false);
const registration = await navigator.serviceWorker.getRegistration();
return registration !== undefined;
}
}
async created(): Promise<void> {
@ -713,9 +718,14 @@ export default class Notifications extends Vue {
}
private async isSubscribed(): Promise<boolean> {
if (!("serviceWorker" in navigator)) return Promise.resolve(false);
const registration = await navigator.serviceWorker.getRegistration();
return (await registration?.pushManager.getSubscription()) != null;
try {
if (!("serviceWorker" in navigator)) return Promise.resolve(false);
const registration = await navigator.serviceWorker.getRegistration();
return (await registration?.pushManager?.getSubscription()) != null;
} catch (e) {
console.error(e);
return Promise.resolve(false);
}
}
private async deleteFeedToken(token: string): Promise<void> {

View File

@ -118,7 +118,7 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Route } from "vue-router";
import { ICurrentUser } from "@/types/current-user.model";
import { LoginError, LoginErrorCode } from "@/types/enums";
@ -259,7 +259,7 @@ export default class Login extends Vue {
await initializeCurrentActor(this.$apollo.provider.defaultClient);
} catch (err) {
if (err instanceof NoIdentitiesException) {
this.$router.push({
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
@ -269,6 +269,13 @@ export default class Login extends Vue {
}
}
}
@Watch("currentUser")
redirectToHomepageIfAlreadyLoggedIn(): Promise<Route> | void {
if (this.currentUser.isLoggedIn) {
return this.$router.push("/");
}
}
}
</script>

View File

@ -6,7 +6,7 @@
{{ $t("Password reset") }}
</h1>
<b-message
title="Error"
:title="$t('Error')"
type="is-danger"
v-for="error in errors"
:key="error"

View File

@ -1,3 +1,6 @@
<template>
<p>{{ $t("Redirecting in progress…") }}</p>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { ICurrentUserRole } from "@/types/enums";
@ -52,11 +55,7 @@ export default class ProviderValidate extends Vue {
);
await this.$router.push({ name: RouteName.HOME });
} else {
// If the user didn't register any profile yet, let's create one for them
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: { email: loggedUser.email, userAlreadyActivated: "true" },
});
// No need to push to REGISTER_PROFILE, the navbar will do it for us
}
}
}

View File

@ -224,7 +224,7 @@ export default class Register extends Vue {
locale: "en",
};
errors: Record<string, unknown> = {};
errors: string[] = [];
sendingForm = false;
@ -245,7 +245,7 @@ export default class Register extends Vue {
this.sendingForm = true;
this.credentials.locale = this.$i18n.locale;
try {
this.errors = {};
this.errors = [];
await this.$apollo.mutate({
mutation: CREATE_USER,
@ -259,11 +259,11 @@ export default class Register extends Vue {
} catch (error) {
console.error(error);
this.errors = error.graphQLErrors.reduce(
(acc: { [key: string]: any }, localError: any) => {
acc[localError.field] = localError.message;
(acc: string[], localError: any) => {
acc.push(localError.message);
return acc;
},
{}
[]
);
this.sendingForm = false;
}

View File

@ -6,13 +6,13 @@ import {
ApolloClient,
ApolloLink,
defaultDataIdFromObject,
fromPromise,
InMemoryCache,
NormalizedCacheObject,
Observable,
split,
} from "@apollo/client/core";
import { RetryLink } from "@apollo/client/link/retry";
import buildCurrentUserResolver from "@/apollo/user";
import { isServerError } from "@/types/apollo";
import { AUTH_ACCESS_TOKEN } from "@/constants";
import { logout } from "@/utils/auth";
import { Socket as PhoenixSocket } from "phoenix";
@ -31,8 +31,8 @@ import { GraphQLError } from "graphql";
// Install the vue plugin
Vue.use(VueApollo);
let refreshingTokenPromise: Promise<boolean> | undefined;
let alreadyRefreshedToken = false;
let isRefreshing = false;
let pendingRequests: any[] = [];
// Endpoints
const httpServer = GRAPHQL_API_ENDPOINT || "http://localhost:4000";
@ -61,9 +61,17 @@ const authMiddleware = new ApolloLink((operation, forward) => {
return null;
});
const customFetch = async (uri: string, options: any) => {
const response = await fetch(uri, options);
if (response.status >= 400) {
return Promise.reject(response.status);
}
return response;
};
const uploadLink = createLink({
uri: httpEndpoint,
fetch,
fetch: customFetch,
});
const phoenixSocket = new PhoenixSocket(wsEndpoint, {
@ -92,32 +100,75 @@ const link = split(
uploadLink
);
const resolvePendingRequests = () => {
pendingRequests.map((callback) => callback());
pendingRequests = [];
};
const isAuthError = (graphQLError: GraphQLError | undefined) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return graphQLError && [403, 401].includes(graphQLError.status_code);
};
const errorLink = onError(
({ graphQLErrors, networkError, forward, operation }) => {
console.debug("We have an apollo error", [graphQLErrors, networkError]);
if (
isServerError(networkError) &&
networkError?.statusCode === 401 &&
!alreadyRefreshedToken
graphQLErrors?.some((graphQLError) => isAuthError(graphQLError)) ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
networkError === 401
) {
if (!refreshingTokenPromise)
refreshingTokenPromise = refreshAccessToken(apolloClient);
console.debug("It's a authorization error (statusCode 401)");
let forwardOperation;
return promiseToObservable(refreshingTokenPromise).flatMap(() => {
refreshingTokenPromise = undefined;
alreadyRefreshedToken = true;
if (!isRefreshing) {
console.debug("Setting isRefreshing to true");
isRefreshing = true;
const context = operation.getContext();
const oldHeaders = context.headers;
forwardOperation = fromPromise(
refreshAccessToken(apolloClient)
.then((res) => {
if (res !== true) {
// failed to refresh the token
throw "Failed to refresh the token";
}
resolvePendingRequests();
operation.setContext({
headers: {
...oldHeaders,
authorization: generateTokenHeader(),
},
});
const context = operation.getContext();
const oldHeaders = context.headers;
return forward(operation);
});
operation.setContext({
headers: {
...oldHeaders,
authorization: generateTokenHeader(),
},
});
return true;
})
.catch((e) => {
console.debug("Something failed, let's logout", e);
pendingRequests = [];
// don't perform a logout since we don't have any working access/refresh tokens
logout(apolloClient, false);
return;
})
.finally(() => {
isRefreshing = false;
})
).filter((value) => Boolean(value));
} else {
forwardOperation = fromPromise(
new Promise((resolve) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
pendingRequests.push(() => resolve());
})
);
}
return forwardOperation.flatMap(() => forward(operation));
}
if (graphQLErrors) {
@ -138,7 +189,12 @@ const errorLink = onError(
}
);
const fullLink = authMiddleware.concat(errorLink).concat(link);
const retryLink = new RetryLink();
const fullLink = authMiddleware
.concat(retryLink)
.concat(errorLink)
.concat(link);
const cache = new InMemoryCache({
addTypename: true,
@ -171,41 +227,3 @@ export default new VueApollo({
console.error(error);
},
});
// Thanks: https://github.com/apollographql/apollo-link/issues/747#issuecomment-502676676
const promiseToObservable = <T>(promise: Promise<T>) =>
new Observable<T>((subscriber) => {
promise.then(
(value) => {
if (subscriber.closed) {
return;
}
subscriber.next(value);
subscriber.complete();
},
(err) => {
console.error("Cannot refresh token.", err);
subscriber.error(err);
logout(apolloClient);
}
);
});
// Manually call this when user log in
// export function onLogin(apolloClient) {
// if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
// }
// Manually call this when user log out
// export async function onLogout() {
// if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
// We don't reset store because we rely on currentUser & currentActor
// which are in the cache (even null). Maybe try to rerun cache init after resetStore?
// try {
// await apolloClient.resetStore();
// } catch (e) {
// // eslint-disable-next-line no-console
// console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
// }
// }

View File

@ -20,7 +20,6 @@ import {
newCommentForEventResponse,
} from "../../mocks/event";
import flushPromises from "flush-promises";
import { InMemoryCache } from "@apollo/client/core";
import { defaultResolvers } from "../../common";
const localVue = createLocalVue();
localVue.use(Buefy);
@ -39,11 +38,9 @@ describe("CommentTree", () => {
let mockClient: MockApolloClient | null;
let apolloProvider;
let requestHandlers: Record<string, RequestHandler>;
const cache = new InMemoryCache({ addTypename: false });
const generateWrapper = (handlers = {}, baseData = {}) => {
mockClient = createMockClient({
cache,
resolvers: defaultResolvers,
});
@ -84,6 +81,21 @@ describe("CommentTree", () => {
});
};
afterEach(() => {
mockClient = null;
requestHandlers = {};
apolloProvider = null;
wrapper.destroy();
});
it("renders a loading comment tree", async () => {
generateWrapper();
expect(wrapper.find(".loading").text()).toBe("Loading comments…");
expect(wrapper.html()).toMatchSnapshot();
});
it("renders a comment tree with comments", async () => {
generateWrapper();
@ -144,12 +156,11 @@ describe("CommentTree", () => {
.fn()
.mockResolvedValue(eventNoCommentThreadsMock),
});
expect(requestHandlers.eventCommentThreadsQueryHandler).toHaveBeenCalled();
expect(wrapper.exists()).toBe(true);
expect(wrapper.find(".loading").text()).toBe("Loading comments…");
await flushPromises();
expect(
requestHandlers.eventCommentThreadsQueryHandler
).toHaveBeenCalledWith({ eventUUID: eventData.uuid });
expect(wrapper.find(".no-comments").text()).toBe("No comments yet");
expect(wrapper.html()).toMatchSnapshot();

View File

@ -33,6 +33,15 @@ exports[`CommentTree renders a comment tree with comments 1`] = `
</div>
`;
exports[`CommentTree renders a loading comment tree 1`] = `
<div>
<!---->
<p class="loading has-text-centered">
Loading comments…
</p>
</div>
`;
exports[`CommentTree renders an empty comment tree 1`] = `
<div>
<form class="new-comment">

View File

@ -16,7 +16,7 @@ const groupDomain = "remotedomain.net";
const groupUsername = `${groupPreferredUsername}@${groupDomain}`;
const defaultSlotText = "A list of elements";
const createSlotButtonText = "+ Post a public message";
const createSlotButtonText = "+ Create a post";
type Props = {
title?: string;

View File

@ -8,7 +8,7 @@ exports[`GroupSection renders group section with basic informations 1`] = `
<div class="main-slot">
<div>A list of elements</div>
</div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Create a post</a></div>
</section>
`;
@ -20,6 +20,6 @@ exports[`GroupSection renders public group section 1`] = `
<div class="main-slot">
<div>A list of elements</div>
</div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Create a post</a></div>
</section>
`;

View File

@ -229,11 +229,7 @@ describe("ParticipationWithoutAccount", () => {
eventData.participantStats.notConfirmed + 1
);
}
// lots of things to await
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.find("form").exists()).toBeFalsy();
expect(wrapper.find("h1.title").text()).toBe(
"Request for participation confirmation sent"

View File

@ -76,8 +76,7 @@ describe("Reset page", () => {
token: "some-token",
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.find("article.message.is-danger").text()).toContain(
"The token you provided is invalid"

View File

@ -115,13 +115,14 @@ describe("Render login form", () => {
expect(requestHandlers.loginMutationHandler).toHaveBeenCalledWith({
...loginMock,
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await flushPromises();
const currentUser = mockClient?.cache.readQuery<{
currentUser: ICurrentUser;
}>({
query: CURRENT_USER_CLIENT,
})?.currentUser;
await flushPromises();
expect(currentUser?.email).toBe("some@email.tld");
expect(currentUser?.id).toBe("1");
expect(jest.isMockFunction(wrapper.vm.$router.push)).toBe(true);

View File

@ -109,6 +109,7 @@ export const eventCommentThreadsMock = {
summary: "I am the senate",
},
deletedAt: null,
insertedAt: "2020-12-03T09:02:00Z",
isAnnouncement: false,
},
{
@ -139,6 +140,7 @@ export const eventCommentThreadsMock = {
summary: "I am the senate",
},
deletedAt: null,
insertedAt: "2020-12-03T11:02:00Z",
isAnnouncement: false,
},
],
@ -183,6 +185,7 @@ export const newCommentForEventResponse: DataMock = {
summary: "I am the senate",
},
deletedAt: null,
insertedAt: "2020-12-03T13:02:00Z",
isAnnouncement: false,
},
},

File diff suppressed because it is too large Load Diff

View File

@ -426,7 +426,7 @@ defmodule Mobilizon.Federation.ActivityPub do
"id" => "#{Endpoint.url()}/leave/event/#{participant.id}"
},
audience <-
Audience.calculate_to_and_cc_from_mentions(participant),
Audience.get_audience(participant),
{:ok, activity} <- create_activity(Map.merge(leave_data, audience), local),
:ok <- maybe_federate(activity) do
{:ok, activity, participant}
@ -803,15 +803,15 @@ defmodule Mobilizon.Federation.ActivityPub do
Scheduler.trigger_notifications_for_participant(participant),
participant_as_data <- Convertible.model_to_as(participant),
audience <-
Audience.calculate_to_and_cc_from_mentions(participant),
update_data <-
Audience.get_audience(participant),
accept_join_data <-
make_accept_join_data(
participant_as_data,
Map.merge(Map.merge(audience, additional), %{
"id" => "#{Endpoint.url()}/accept/join/#{participant.id}"
})
) do
{:ok, participant, update_data}
{:ok, participant, accept_join_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
@ -837,15 +837,15 @@ defmodule Mobilizon.Federation.ActivityPub do
),
member_as_data <- Convertible.model_to_as(member),
audience <-
Audience.calculate_to_and_cc_from_mentions(member),
update_data <-
Audience.get_audience(member),
accept_join_data <-
make_accept_join_data(
member_as_data,
Map.merge(Map.merge(audience, additional), %{
"id" => "#{Endpoint.url()}/accept/join/#{member.id}"
})
) do
{:ok, member, update_data}
{:ok, member, accept_join_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
@ -899,7 +899,7 @@ defmodule Mobilizon.Federation.ActivityPub do
participant_as_data <- Convertible.model_to_as(participant),
audience <-
participant
|> Audience.calculate_to_and_cc_from_mentions()
|> Audience.get_audience()
|> Map.merge(additional),
reject_data <- %{
"type" => "Reject",
@ -925,7 +925,7 @@ defmodule Mobilizon.Federation.ActivityPub do
with {:ok, %Follower{} = follower} <- Actors.delete_follower(follower),
follower_as_data <- Convertible.model_to_as(follower),
audience <-
follower.actor |> Audience.calculate_to_and_cc_from_mentions() |> Map.merge(additional),
follower.actor |> Audience.get_audience() |> Map.merge(additional),
reject_data <- %{
"to" => [follower.actor.url],
"type" => "Reject",

View File

@ -55,6 +55,10 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
{:error, "Can't make a local actor from URL"}
else
case Fetcher.fetch_and_prepare_actor_from_url(url) do
# Just in case
{:ok, {:error, _e}} ->
raise ArgumentError, message: "Failed to make actor from url #{url}"
{:ok, data} ->
Actors.upsert_actor(data, preload)
@ -67,7 +71,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
{:error, :http_error}
{:error, e} ->
Logger.warn("Failed to make actor from url")
Logger.warn("Failed to make actor from url #{url}")
{:error, e}
end
end

View File

@ -3,18 +3,102 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
Tools for calculating content audience
"""
alias Mobilizon.Actors
alias Mobilizon.{Actors, Events, Share}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Posts.Post
alias Mobilizon.Share
alias Mobilizon.Storage.Repo
require Logger
@ap_public "https://www.w3.org/ns/activitystreams#Public"
@type audience :: %{required(String.t()) => list(String.t())}
@doc """
Get audience for an entity
"""
@spec get_audience(Entity.t()) :: audience()
def get_audience(%Event{} = event) do
extract_actors_from_event(event)
end
def get_audience(%Post{draft: true} = post) do
get_audience(%Post{post | visibility: :private, draft: false})
end
def get_audience(%Post{attributed_to: %Actor{} = group, visibility: visibility}) do
{to, cc} = get_to_and_cc(group, [], visibility)
%{"to" => to, "cc" => cc}
end
def get_audience(%Discussion{actor: actor}) do
%{"to" => maybe_add_group_members([], actor), "cc" => []}
end
# Deleted comments are just like tombstones
def get_audience(%Comment{deleted_at: deleted_at}) when not is_nil(deleted_at) do
%{"to" => [@ap_public], "cc" => []}
end
def get_audience(%Comment{discussion: %Discussion{} = discussion}) do
get_audience(discussion)
end
def get_audience(%Comment{
mentions: mentions,
actor: %Actor{} = actor,
visibility: visibility,
in_reply_to_comment: in_reply_to_comment,
event: event,
origin_comment: origin_comment,
url: url
}) do
with {to, cc} <-
extract_actors_from_mentions(mentions, actor, visibility),
{to, cc} <- {to ++ add_in_reply_to(in_reply_to_comment), cc},
{to, cc} <- add_event_organizers(event, to, cc),
{to, cc} <-
{to,
cc ++
add_comments_authors([origin_comment]) ++
add_shares_actors_followers(url)} do
%{"to" => Enum.uniq(to), "cc" => Enum.uniq(cc)}
end
end
def get_audience(%Participant{} = participant) do
%Event{} = event = Events.get_event_with_preload!(participant.event_id)
%Actor{} = organizer = group_or_organizer_event(event)
cc =
event.id
|> Mobilizon.Events.list_actors_participants_for_event()
|> Enum.map(& &1.url)
|> Enum.filter(&(&1 != participant.actor.url))
|> maybe_add_group_members(organizer)
|> maybe_add_followers(organizer)
%{
"to" => [participant.actor.url, organizer.url],
"cc" => cc
}
end
def get_audience(%Member{} = member) do
%{"to" => [member.parent.url, member.parent.members_url], "cc" => []}
end
def get_audience(%Actor{} = actor) do
%{
"to" => [@ap_public],
"cc" =>
maybe_add_group_members([actor.followers_url], actor) ++
add_actors_that_had_our_content(actor.id)
}
end
@doc """
Determines the full audience based on mentions for an audience
@ -39,6 +123,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
to = [@ap_public | mentions]
cc = [actor.followers_url]
cc = maybe_add_group_members(cc, actor)
{to, cc}
end
@ -47,13 +133,18 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
to = [actor.followers_url | mentions]
cc = [@ap_public]
to = maybe_add_group_members(to, actor)
{to, cc}
end
@spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, :private) do
{to, cc} = get_to_and_cc(actor, mentions, :direct)
{[actor.followers_url | to], cc}
to = maybe_add_group_members(to, actor)
{to, cc}
end
@spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
@ -65,125 +156,42 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
{mentions, []}
end
@spec maybe_add_group_members(List.t(), Actor.t()) :: List.t()
defp maybe_add_group_members(collection, %Actor{type: :Group, members_url: members_url}) do
[members_url | collection]
end
defp maybe_add_group_members(collection, %Actor{type: _}), do: collection
@spec maybe_add_followers(List.t(), Actor.t()) :: List.t()
defp maybe_add_followers(collection, %Actor{type: :Group, followers_url: followers_url}) do
[followers_url | collection]
end
defp maybe_add_followers(collection, %Actor{type: _}), do: collection
def get_addressed_actors(mentioned_users, _), do: mentioned_users
def calculate_to_and_cc_from_mentions(
%Comment{discussion: %Discussion{actor_id: actor_id}} = _comment
) do
with %Actor{type: :Group, members_url: members_url} <- Actors.get_actor(actor_id) do
%{"to" => [members_url], "cc" => []}
end
end
def calculate_to_and_cc_from_mentions(%Comment{} = comment) do
with {to, cc} <-
extract_actors_from_mentions(comment.mentions, comment.actor, comment.visibility),
{to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc},
{to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc},
{to, cc} <-
{to,
Enum.uniq(
cc ++
add_comments_authors([comment.origin_comment]) ++
add_shares_actors_followers(comment.url)
)} do
%{"to" => to, "cc" => cc}
end
end
def calculate_to_and_cc_from_mentions(%Discussion{actor_id: actor_id}) do
with %Actor{type: :Group, members_url: members_url} <- Actors.get_actor(actor_id) do
%{"to" => [members_url], "cc" => []}
end
end
def calculate_to_and_cc_from_mentions(
%Event{
attributed_to: %Actor{members_url: members_url},
visibility: visibility
} = event
) do
%{"to" => to, "cc" => cc} = extract_actors_from_event(event)
case visibility do
:public ->
%{"to" => [@ap_public, members_url] ++ to, "cc" => [] ++ cc}
:unlisted ->
%{"to" => [members_url] ++ to, "cc" => [@ap_public] ++ cc}
:private ->
# Private is restricted to only the members
%{"to" => [members_url], "cc" => []}
end
end
def calculate_to_and_cc_from_mentions(%Event{} = event) do
extract_actors_from_event(event)
end
def calculate_to_and_cc_from_mentions(%Post{
attributed_to: %Actor{members_url: members_url, followers_url: followers_url},
visibility: visibility,
draft: draft
}) do
cond do
# If the post is draft we send it only to members
draft == true ->
%{"to" => [members_url], "cc" => []}
# If public everyone
visibility == :public ->
%{"to" => [@ap_public, members_url], "cc" => [followers_url]}
# Otherwise just followers
visibility == :unlisted ->
%{"to" => [followers_url, members_url], "cc" => [@ap_public]}
visibility == :private ->
# Private is restricted to only the members
%{"to" => [members_url], "cc" => []}
true ->
%{"to" => [], "cc" => []}
end
end
def calculate_to_and_cc_from_mentions(%Participant{} = participant) do
participant = Repo.preload(participant, [:actor, :event])
actor_participants_urls =
participant.event.id
|> Mobilizon.Events.list_actors_participants_for_event()
|> Enum.map(& &1.url)
%{"to" => [participant.actor.url], "cc" => actor_participants_urls}
end
def calculate_to_and_cc_from_mentions(%Member{} = member) do
member = Repo.preload(member, [:parent])
%{"to" => [member.parent.members_url], "cc" => []}
end
def calculate_to_and_cc_from_mentions(%Actor{} = actor) do
%{
"to" => [@ap_public],
"cc" => [actor.followers_url] ++ add_actors_that_had_our_content(actor.id)
}
end
defp add_in_reply_to(%Comment{actor: %Actor{url: url}} = _comment), do: [url]
defp add_in_reply_to(%Event{organizer_actor: %Actor{url: url}} = _event), do: [url]
defp add_in_reply_to(_), do: []
defp add_event_author(nil), do: []
defp add_event_organizers(%Event{} = event, to, cc) do
event = Repo.preload(event, [:organizer_actor, :attributed_to])
defp add_event_author(%Event{} = event) do
[Repo.preload(event, [:organizer_actor]).organizer_actor.url]
case event do
%Event{
attributed_to: %Actor{members_url: members_url, followers_url: followers_url},
organizer_actor: %Actor{url: organizer_actor_url}
} ->
{to ++ [organizer_actor_url, members_url], cc ++ [followers_url]}
%Event{organizer_actor: %Actor{url: organizer_actor_url}} ->
{to ++ [organizer_actor_url], cc}
end
end
defp add_comment_author(nil), do: nil
defp add_event_organizers(_, to, cc), do: {to, cc}
defp add_comment_author(%Comment{} = comment) do
case Repo.preload(comment, [:actor]) do
@ -195,6 +203,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
end
end
defp add_comment_author(_), do: nil
defp add_comments_authors(comments) do
authors =
comments
@ -208,8 +218,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
defp add_shares_actors_followers(uri) do
uri
|> Share.get_actors_by_share_uri()
|> Enum.map(&Actors.list_followers_actors_for_actor/1)
|> List.flatten()
|> Enum.map(& &1.url)
|> Enum.uniq()
end
@ -217,8 +225,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
defp add_actors_that_had_our_content(actor_id) do
actor_id
|> Share.get_actors_by_owner_actor_id()
|> Enum.map(&Actors.list_followers_actors_for_actor/1)
|> List.flatten()
|> Enum.map(& &1.url)
|> Enum.uniq()
end
@ -241,7 +247,11 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
defp extract_actors_from_event(%Event{} = event) do
with {to, cc} <-
extract_actors_from_mentions(event.mentions, event.organizer_actor, event.visibility),
extract_actors_from_mentions(
event.mentions,
group_or_organizer_event(event),
event.visibility
),
{to, cc} <-
{to,
Enum.uniq(
@ -253,4 +263,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
%{"to" => [], "cc" => []}
end
end
@spec group_or_organizer_event(Event.t()) :: Actor.t()
defp group_or_organizer_event(%Event{attributed_to: %Actor{} = group}), do: group
defp group_or_organizer_event(%Event{organizer_actor: %Actor{} = actor}), do: actor
end

View File

@ -142,7 +142,7 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
true
else
Sentry.capture_message("Object origin check failed", extra: %{url: url, data: data})
Logger.debug("Object origin check failed")
Logger.debug("Object origin check failed between #{inspect(url)} and #{inspect(data)}")
false
end
end

View File

@ -0,0 +1,134 @@
defmodule Mobilizon.Federation.ActivityPub.Permission do
@moduledoc """
Module to check group members permissions on objects
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Types.{Entity, Ownable}
require Logger
use StructAccess
defstruct [:access, :create, :update, :delete]
@member_roles [:member, :moderator, :administrator]
@doc """
Check that actor can access the object
"""
@spec can_access_group_object?(Actor.t(), Entity.t()) :: boolean()
def can_access_group_object?(%Actor{} = actor, object) do
can_manage_group_object?(:access, actor, object)
end
@doc """
Check that actor can create such an object
"""
@spec can_create_group_object?(String.t() | integer(), String.t() | integer(), Entity.t()) ::
boolean()
def can_create_group_object?(
actor_id,
group_id,
object
) do
case object |> Ownable.permissions() |> get_in([:create]) do
:member ->
Actors.is_member?(actor_id, group_id)
:moderator ->
Actors.is_moderator?(actor_id, group_id)
:administrator ->
Actors.is_administrator?(actor_id, group_id)
_ ->
false
end
end
@doc """
Check that actor can update the object
"""
@spec can_update_group_object?(Actor.t(), Entity.t()) :: boolean()
def can_update_group_object?(%Actor{} = actor, object) do
can_manage_group_object?(:update, actor, object)
end
@doc """
Check that actor can delete the object
"""
@spec can_delete_group_object?(Actor.t(), Entity.t()) :: boolean()
def can_delete_group_object?(%Actor{} = actor, object) do
can_manage_group_object?(:delete, actor, object)
end
@type existing_object_permissions :: :access | :update | :delete
@spec can_manage_group_object?(
existing_object_permissions(),
Actor.t(),
any()
) :: boolean()
defp can_manage_group_object?(permission, %Actor{url: actor_url} = actor, object) do
if Ownable.group_actor(object) != nil do
case object |> Ownable.permissions() |> get_in([permission]) do
role when role in @member_roles ->
activity_actor_is_group_member?(actor, object, role)
_ ->
case permission do
:access ->
Logger.warn("Actor #{actor_url} can't access #{object.url}")
:update ->
Logger.warn("Actor #{actor_url} can't update #{object.url}")
:delete ->
Logger.warn("Actor #{actor_url} can't delete #{object.url}")
end
false
end
else
true
end
end
@spec activity_actor_is_group_member?(Actor.t(), Entity.t(), atom()) :: boolean()
defp activity_actor_is_group_member?(
%Actor{id: actor_id, url: actor_url},
object,
role
) do
case Ownable.group_actor(object) do
%Actor{type: :Group, id: group_id, url: group_url} ->
Logger.debug("Group object url is #{group_url}")
case role do
:moderator ->
Logger.debug(
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
)
Actors.is_moderator?(actor_id, group_id)
:administrator ->
Logger.debug(
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
)
Actors.is_administrator?(actor_id, group_id)
_ ->
Logger.debug(
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
)
Actors.is_member?(actor_id, group_id)
end
_ ->
false
end
end
end

View File

@ -14,7 +14,7 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
@spec maybe_preload(struct()) :: {:ok, struct()} | {:error, struct()}
def maybe_preload(%Event{url: url}),
do: {:ok, Events.get_public_event_by_url_with_preload!(url)}
do: {:ok, Events.get_event_by_url!(url)}
def maybe_preload(%Comment{url: url}),
do: {:ok, Discussions.get_comment_from_url_with_preload!(url)}

View File

@ -17,7 +17,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Utils}
alias Mobilizon.Federation.ActivityPub.{Activity, Permission, Relay, Utils}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Types.Ownable
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
@ -409,7 +409,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:origin_check, true} <-
{:origin_check,
Utils.origin_check?(actor_url, update_data) ||
Utils.can_update_group_object?(actor, old_event)},
Permission.can_update_group_object?(actor, old_event)},
{:ok, %Activity{} = activity, %Event{} = new_event} <-
ActivityPub.update(old_event, object_data, false) do
{:ok, activity, new_event}
@ -454,7 +454,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:origin_check, true} <-
{:origin_check,
Utils.origin_check?(actor_url, update_data["object"]) ||
Utils.can_update_group_object?(actor, old_post)},
Permission.can_update_group_object?(actor, old_post)},
{:ok, %Activity{} = activity, %Post{} = new_post} <-
ActivityPub.update(old_post, object_data, false) do
{:ok, activity, new_post}
@ -482,7 +482,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:origin_check, true} <-
{:origin_check,
Utils.origin_check?(actor_url, update_data) ||
Utils.can_update_group_object?(actor, old_resource)},
Permission.can_update_group_object?(actor, old_resource)},
{:ok, %Activity{} = activity, %Resource{} = new_resource} <-
ActivityPub.update(old_resource, object_data, false) do
{:ok, activity, new_resource}
@ -585,7 +585,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:origin_check, true} <-
{:origin_check,
Utils.origin_check_from_id?(actor_url, object_id) ||
Utils.can_delete_group_object?(actor, object)},
Permission.can_delete_group_object?(actor, object)},
{:ok, activity, object} <- ActivityPub.delete(object, actor, false) do
{:ok, activity, object}
else
@ -629,7 +629,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:origin_check, true} <-
{:origin_check,
Utils.origin_check?(actor_url, data) ||
Utils.can_update_group_object?(actor, old_resource)},
Permission.can_update_group_object?(actor, old_resource)},
{:ok, activity, new_resource} <- ActivityPub.move(:resource, old_resource, object_data) do
{:ok, activity, new_resource}
else
@ -837,7 +837,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
case get_participant(join_object) do
case get_participant(join_object, actor_accepting) do
{:ok, participant} ->
do_handle_incoming_accept_join_event(participant, actor_accepting)
@ -868,9 +868,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
%Actor{} = actor_accepting
)
when role in [:not_approved, :rejected] do
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
with {:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
with %Event{} = event <- Events.get_event_with_preload!(event.id),
{:can_accept_event_join, true} <-
{:can_accept_event_join, can_manage_event?(actor_accepting, event)},
{:ok, %Activity{} = activity, %Participant{role: :participant} = participant} <-
ActivityPub.accept(
:join,
@ -881,8 +881,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
Participation.send_emails_to_local_user(participant) do
{:ok, activity, participant}
else
{:same_actor} ->
{:error, "Actor who accepted the join wasn't the event organizer. Quite odd."}
{:can_accept_event_join, false} ->
{:error, "Actor who accepted the join didn't have permission to do so."}
{:ok, %Participant{role: :participant} = _follow} ->
{:error, "Participant"}
@ -902,7 +902,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
type
)
when role in [:not_approved, :rejected, :invited] and type in [:join, :invite] do
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
with {:ok, %Activity{} = activity, %Member{role: :member} = member} <-
ActivityPub.accept(
@ -918,10 +917,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
defp do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
with {:join_event, {:ok, %Participant{event: event, role: role} = participant}}
when role != :rejected <-
{:join_event, get_participant(join_object)},
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
{:join_event, get_participant(join_object, actor_accepting)},
{:event, %Event{} = event} <- {:event, Events.get_event_with_preload!(event.id)},
{:can_accept_event_reject, true} <-
{:can_accept_event_reject, can_manage_event?(actor_accepting, event)},
{:ok, activity, participant} <-
ActivityPub.reject(:join, participant, false),
:ok <- Participation.send_emails_to_local_user(participant) do
@ -1026,14 +1025,22 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end
end
defp get_participant(join_object) do
defp get_participant(join_object, %Actor{} = actor_accepting, loop \\ 1) do
with join_object_id when not is_nil(join_object_id) <- Utils.get_url(join_object),
{:not_found, %Participant{} = participant} <-
{:not_found, Events.get_participant_by_url(join_object_id)} do
{:ok, participant}
else
{:not_found, _err} ->
{:error, "Participant URL not found"}
with true <- is_map(join_object),
true <- loop < 2,
true <- Utils.are_same_origin?(actor_accepting.url, join_object["id"]),
{:ok, _activity, %Participant{url: participant_url}} <- handle_incoming(join_object) do
get_participant(participant_url, actor_accepting, 2)
else
_ ->
{:error, "Participant URL not found"}
end
_ ->
{:error, "ActivityPub ID not found in Accept Join object"}
@ -1103,6 +1110,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:error, error_message, object} when error_message in ["Gone", "Not found"] ->
{:ok, object}
# comments are just emptied
{:ok, %Comment{deleted_at: deleted_at} = object} when not is_nil(deleted_at) ->
{:ok, object}
{:ok, %{url: url} = object} ->
if Utils.are_same_origin?(url, Endpoint.url()),
do: {:ok, object},
@ -1130,4 +1141,22 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
_ -> {:error, :remove_object_not_found}
end
end
defp can_manage_event?(
%Actor{url: actor_url} = actor,
%Event{attributed_to: %Actor{type: :Group, url: group_url} = _group} = event
) do
actor_url == group_url || Permission.can_update_group_object?(actor, event)
end
defp can_manage_event?(
%Actor{id: actor_id},
%Event{organizer_actor: %Actor{id: organizer_actor_id}}
) do
organizer_actor_id == actor_id
end
defp can_manage_event?(_actor, _event) do
false
end
end

View File

@ -3,7 +3,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Audience, Relay}
alias Mobilizon.Federation.ActivityPub.{Audience, Permission, Relay}
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
@ -47,7 +47,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
actor_as_data <- Convertible.model_to_as(new_actor),
{:ok, true} <- Cachex.del(:activity_pub, "actor_#{new_actor.preferred_username}"),
audience <-
Audience.calculate_to_and_cc_from_mentions(new_actor),
Audience.get_audience(new_actor),
additional <- Map.merge(additional, %{"actor" => old_actor.url}),
update_data <- make_update_data(actor_as_data, Map.merge(audience, additional)) do
{:ok, new_actor, update_data}
@ -104,8 +104,14 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
def group_actor(%Actor{} = actor), do: actor
def role_needed_to_update(%Actor{} = _group), do: :administrator
def role_needed_to_delete(%Actor{} = _group), do: :administrator
def permissions(%Actor{} = _group) do
%Permission{
access: :member,
create: nil,
update: :administrator,
delete: :administrator
}
end
@spec join(Actor.t(), Actor.t(), boolean(), map()) :: {:ok, map(), Member.t()}
def join(%Actor{type: :Group} = group, %Actor{} = actor, _local, additional) do
@ -136,7 +142,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
"object" => group.url
},
audience <-
Audience.calculate_to_and_cc_from_mentions(member) do
Audience.get_audience(member) do
approve_if_default_role_is_member(
group,
actor,

Some files were not shown because too many files have changed in this diff Show More