🛠️ Add services index and landing page components with dynamic links and new Polish translations

This commit is contained in:
HolgerHatGarKeineNode
2025-12-07 00:01:15 +01:00
parent bc700a1f2c
commit aef4deedd6
25 changed files with 2427 additions and 878 deletions

View File

@@ -69,7 +69,10 @@ This application is a Laravel application and its main Laravel ecosystems packag
- php - 8.4.14
- laravel/framework (LARAVEL) - v12
- laravel/horizon (HORIZON) - v5
- laravel/nightwatch (NIGHTWATCH) - v1
- laravel/prompts (PROMPTS) - v0
- laravel/sanctum (SANCTUM) - v4
- livewire/flux (FLUXUI_FREE) - v2
- livewire/flux-pro (FLUXUI_PRO) - v2
- livewire/livewire (LIVEWIRE) - v3
@@ -283,7 +286,7 @@ protected function isAccessible(User $user, ?string $path = null): bool
This is correct as of Boost installation, but there may be additional components within the codebase.
<available-flux-components>
accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, context, date-picker, dropdown, editor, field, heading, file upload, icon, input, modal, navbar, pagination, pillbox, popover, profile, radio, select, separator, switch, table, tabs, text, textarea, toast, tooltip
accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip
</available-flux-components>

11
.mcp.json Normal file
View File

@@ -0,0 +1,11 @@
{
"mcpServers": {
"laravel-boost": {
"command": "vendor/bin/sail",
"args": [
"artisan",
"boost:mcp"
]
}
}
}

576
CLAUDE.md Normal file
View File

@@ -0,0 +1,576 @@
<laravel-boost-guidelines>
=== foundation rules ===
# Laravel Boost Guidelines
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.14
- laravel/framework (LARAVEL) - v12
- laravel/horizon (HORIZON) - v5
- laravel/nightwatch (NIGHTWATCH) - v1
- laravel/prompts (PROMPTS) - v0
- laravel/sanctum (SANCTUM) - v4
- livewire/flux (FLUXUI_FREE) - v2
- livewire/flux-pro (FLUXUI_PRO) - v2
- livewire/livewire (LIVEWIRE) - v3
- livewire/volt (VOLT) - v1
- laravel/mcp (MCP) - v0
- laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v3
- phpunit/phpunit (PHPUNIT) - v11
- tailwindcss (TAILWINDCSS) - v4
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.
## Verification Scripts
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
## Application Structure & Architecture
- Stick to existing directory structure - don't create new base folders without approval.
- Do not change the application's dependencies without approval.
## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail yarn run build`, `vendor/bin/sail yarn run dev`, or `vendor/bin/sail composer run dev`. Ask them.
## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
## Documentation Files
- You must only create documentation files if explicitly requested by the user.
=== boost rules ===
## Laravel Boost
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
## Artisan
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters.
## URLs
- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port.
## Tinker / Debugging
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
- Use the `database-query` tool when you only need to read from the database.
## Reading Browser Logs With the `browser-logs` Tool
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
- Only recent browser logs will be useful - ignore old logs.
## Searching Documentation (Critically Important)
- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches.
- Search the documentation before making code changes to ensure we are taking the correct approach.
- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
### Available Search Syntax
- You can and should pass multiple queries at once. The most relevant results will be returned first.
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit"
3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit"
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms
=== php rules ===
## PHP
- Always use curly braces for control structures, even if it has one line.
### Constructors
- Use PHP 8 constructor property promotion in `__construct()`.
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
- Do not allow empty `__construct()` methods with zero parameters.
### Type Declarations
- Always use explicit return type declarations for methods and functions.
- Use appropriate PHP type hints for method parameters.
<code-snippet name="Explicit Return Types and Method Params" lang="php">
protected function isAccessible(User $user, ?string $path = null): bool
{
...
}
</code-snippet>
## Comments
- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on.
## PHPDoc Blocks
- Add useful array shape type definitions for arrays when appropriate.
## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== sail rules ===
## Laravel Sail
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
- Open the application in the browser by running `vendor/bin/sail open`.
- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples:
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
- Install Composer packages: `vendor/bin/sail composer install`
- Execute node commands: `vendor/bin/sail yarn run dev`
- Execute PHP scripts: `vendor/bin/sail php [script]`
- View all available Sail commands by running `vendor/bin/sail` without arguments.
=== tests rules ===
## Test Enforcement
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter.
=== laravel/core rules ===
## Do Things the Laravel Way
- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
- Use Eloquent models and relationships before suggesting raw database queries
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
- Generate code that prevents N+1 query problems by using eager loading.
- Use Laravel's query builder for very complex database operations.
### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
### Controllers & Validation
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
- Check sibling Form Requests to see if the application uses array or string based validation rules.
### Queues
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
### Authentication & Authorization
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
### URL Generation
- When generating links to other pages, prefer named routes and the `route()` function.
### Configuration
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail yarn run build` or ask the user to run `vendor/bin/sail yarn run dev` or `vendor/bin/sail composer run dev`.
=== laravel/v12 rules ===
## Laravel 12
- Use the `search-docs` tool to get version specific documentation.
- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.
### Laravel 12 Structure
- No middleware files in `app/Http/Middleware/`.
- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
- `bootstrap/providers.php` contains application specific service providers.
- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration.
- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration.
### Database
- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
### Models
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
=== fluxui-pro/core rules ===
## Flux UI Pro
- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants.
- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize.
- You should use Flux UI components when available.
- Fallback to standard Blade components if Flux is unavailable.
- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project.
- Flux UI components look like this:
<code-snippet name="Flux UI component usage example" lang="blade">
<flux:button variant="primary"/>
</code-snippet>
### Available Components
This is correct as of Boost installation, but there may be additional components within the codebase.
<available-flux-components>
accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip
</available-flux-components>
=== livewire/core rules ===
## Livewire Core
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components
- State should live on the server, with the UI reflecting it.
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
## Livewire Best Practices
- Livewire components require a single root element.
- Use `wire:loading` and `wire:dirty` for delightful loading states.
- Add `wire:key` in loops:
```blade
@foreach ($items as $item)
<div wire:key="item-{{ $item->id }}">
{{ $item->name }}
</div>
@endforeach
```
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
<code-snippet name="Lifecycle hook examples" lang="php">
public function mount(User $user) { $this->user = $user; }
public function updatedSearch() { $this->resetPage(); }
</code-snippet>
## Testing Livewire
<code-snippet name="Example Livewire component test" lang="php">
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->assertSee(1)
->assertStatus(200);
</code-snippet>
<code-snippet name="Testing a Livewire component exists within a page" lang="php">
$this->get('/posts/create')
->assertSeeLivewire(CreatePost::class);
</code-snippet>
=== livewire/v3 rules ===
## Livewire 3
### Key Changes From Livewire 2
- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions.
- Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default.
- Components now use the `App\Livewire` namespace (not `App\Http\Livewire`).
- Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`).
- Use the `components.layouts.app` view as the typical layout path (not `layouts.app`).
### New Directives
- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples.
### Alpine
- Alpine is now included with Livewire, don't manually include Alpine.js.
- Plugins included with Alpine: persist, intersect, collapse, and focus.
### Lifecycle Hooks
- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring:
<code-snippet name="livewire:load example" lang="js">
document.addEventListener('livewire:init', function () {
Livewire.hook('request', ({ fail }) => {
if (fail && fail.status === 419) {
alert('Your session expired');
}
});
Livewire.hook('message.failed', (message, component) => {
console.error(message);
});
});
</code-snippet>
=== volt/core rules ===
## Livewire Volt
- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it.
- Make new Volt components using `vendor/bin/sail artisan make:volt [name] [--test] [--pest]`
- Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file
- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive.
- You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component.
### Volt Functional Component Example
<code-snippet name="Volt Functional Component Example" lang="php">
@volt
<?php
use function Livewire\Volt\{state, computed};
state(['count' => 0]);
$increment = fn () => $this->count++;
$decrement = fn () => $this->count--;
$double = computed(fn () => $this->count * 2);
?>
<div>
<h1>Count: {{ $count }}</h1>
<h2>Double: {{ $this->double }}</h2>
<button wire:click="increment">+</button>
<button wire:click="decrement">-</button>
</div>
@endvolt
</code-snippet>
### Volt Class Based Component Example
To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax:
<code-snippet name="Volt Class-based Volt Component Example" lang="php">
use Livewire\Volt\Component;
new class extends Component {
public $count = 0;
public function increment()
{
$this->count++;
}
} ?>
<div>
<h1>{{ $count }}</h1>
<button wire:click="increment">+</button>
</div>
</code-snippet>
### Testing Volt & Volt Components
- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`.
<code-snippet name="Livewire Test Example" lang="php">
use Livewire\Volt\Volt;
test('counter increments', function () {
Volt::test('counter')
->assertSee('Count: 0')
->call('increment')
->assertSee('Count: 1');
});
</code-snippet>
<code-snippet name="Volt Component Test Using Pest" lang="php">
declare(strict_types=1);
use App\Models\{User, Product};
use Livewire\Volt\Volt;
test('product form creates product', function () {
$user = User::factory()->create();
Volt::test('pages.products.create')
->actingAs($user)
->set('form.name', 'Test Product')
->set('form.description', 'Test Description')
->set('form.price', 99.99)
->call('create')
->assertHasNoErrors();
expect(Product::where('name', 'Test Product')->exists())->toBeTrue();
});
</code-snippet>
### Common Patterns
<code-snippet name="CRUD With Volt" lang="php">
<?php
use App\Models\Product;
use function Livewire\Volt\{state, computed};
state(['editing' => null, 'search' => '']);
$products = computed(fn() => Product::when($this->search,
fn($q) => $q->where('name', 'like', "%{$this->search}%")
)->get());
$edit = fn(Product $product) => $this->editing = $product->id;
$delete = fn(Product $product) => $product->delete();
?>
<!-- HTML / UI Here -->
</code-snippet>
<code-snippet name="Real-Time Search With Volt" lang="php">
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="Search..."
/>
</code-snippet>
<code-snippet name="Loading States With Volt" lang="php">
<flux:button wire:click="save" wire:loading.attr="disabled">
<span wire:loading.remove>Save</span>
<span wire:loading>Saving...</span>
</flux:button>
</code-snippet>
=== pint/core rules ===
## Laravel Pint Code Formatter
- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues.
=== pest/core rules ===
## Pest
### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests
- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
- Pest tests look and behave like this:
<code-snippet name="Basic Pest Test Example" lang="php">
it('is true', function () {
expect(true)->toBeTrue();
});
</code-snippet>
### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `vendor/bin/sail artisan test`.
- To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions
- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.:
<code-snippet name="Pest Example Asserting postJson Response" lang="php">
it('returns all', function () {
$response = $this->postJson('/api/docs', []);
$response->assertSuccessful();
});
</code-snippet>
### Mocking
- Mocking can be very helpful when appropriate.
- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do.
- You can also create partial mocks using the same import or self method.
### Datasets
- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules.
<code-snippet name="Pest Dataset Example" lang="php">
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with([
'james' => 'james@laravel.com',
'taylor' => 'taylor@laravel.com',
]);
</code-snippet>
=== tailwindcss/core rules ===
## Tailwind Core
- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own.
- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..)
- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically
- You can use the `search-docs` tool to get exact examples from the official documentation when needed.
### Spacing
- When listing items, use gap utilities for spacing, don't use margins.
<code-snippet name="Valid Flex Gap Spacing Example" lang="html">
<div class="flex gap-8">
<div>Superior</div>
<div>Michigan</div>
<div>Erie</div>
</div>
</code-snippet>
### Dark Mode
- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`.
=== tailwindcss/v4 rules ===
## Tailwind 4
- Always use Tailwind CSS v4 - do not use the deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.
- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed.
<code-snippet name="Extending Theme in CSS" lang="css">
@theme {
--color-brand: oklch(0.72 0.11 178);
}
</code-snippet>
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
</code-snippet>
### Replaced Utilities
- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement.
- Opacity values are still numeric.
| Deprecated | Replacement |
|------------+--------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |
</laravel-boost-guidelines>

View File

@@ -160,6 +160,26 @@ class SeoDataAttribute
description: __('Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.'),
image: $domainImage,
),
'services_create' => new SEOData(
title: __('Neuen Service erstellen'),
description: __('Füge einen neuen Self-Hosted Service zur Bitcoin Community hinzu.'),
image: $domainImage,
),
'services_edit' => new SEOData(
title: __('Service bearbeiten'),
description: __('Aktualisiere die Details deines Self-Hosted Service.'),
image: $domainImage,
),
'services_index' => new SEOData(
title: __('Self-Hosted Services - Übersicht'),
description: __('Entdecke Bitcoin Self-Hosted Services und dezentrale Angebote der Community.'),
image: $domainImage,
),
'services_landingpage' => new SEOData(
title: __('Service Details'),
description: __('Erfahre mehr über diesen Self-Hosted Service aus der Bitcoin Community.'),
image: $domainImage,
),
// Add more as needed
'default' => new SEOData(
title: __('Willkommen'),

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums;
enum SelfHostedServiceType: string
{
case Mempool = 'mempool';
case LNbits = 'lnbits';
case Alby = 'alby';
case Other = 'other';
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Models;
use App\Enums\SelfHostedServiceType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Cookie;
use Spatie\Image\Enums\Fit;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Tags\HasTags;
class SelfHostedService extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia;
use HasSlug;
use HasTags;
protected $guarded = [];
protected $casts = [
'id' => 'integer',
'created_by' => 'integer',
'type' => SelfHostedServiceType::class,
];
protected static function booted(): void
{
static::creating(function ($model): void {
// Only set created_by if user is authenticated and not explicitly set as anonymous
if (auth()->check() && !isset($model->created_by)) {
$model->created_by = auth()->id();
}
});
}
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom(['name'])
->saveSlugsTo('slug')
->usingLanguage(Cookie::get('lang', config('app.locale')));
}
public function registerMediaConversions(Media $media = null): void
{
$this
->addMediaConversion('preview')
->fit(Fit::Crop, 300, 300)
->nonQueued();
$this
->addMediaConversion('thumb')
->fit(Fit::Crop, 130, 130)
->width(130)
->height(130);
}
public function registerMediaCollections(): void
{
$this
->addMediaCollection('logo')
->singleFile()
->useFallbackUrl(asset('img/einundzwanzig.png'));
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}

View File

@@ -1,8 +1,10 @@
{
"agents": [
"claude_code",
"phpstorm"
],
"editors": [
"claude_code",
"phpstorm"
],
"guidelines": [],

1219
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
<?php
namespace Database\Factories;
use App\Enums\SelfHostedServiceType;
use App\Models\SelfHostedService;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<SelfHostedService>
*/
class SelfHostedServiceFactory extends Factory
{
protected $model = SelfHostedService::class;
public function definition(): array
{
$name = $this->faker->unique()->company();
return [
'created_by' => User::factory(),
'name' => $name,
'slug' => str($name)->slug(),
'intro' => $this->faker->optional()->paragraph(),
'url_clearnet' => $this->faker->optional()->url(),
'url_onion' => null,
'url_i2p' => null,
'url_pkdns' => null,
'type' => $this->faker->randomElement(SelfHostedServiceType::cases())->value,
'contact_url' => $this->faker->optional()->url(),
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('self_hosted_services', function (Blueprint $table): void {
$table->id();
$table->foreignId('created_by')->nullable()->constrained('users')->cascadeOnDelete()->cascadeOnUpdate();
$table->string('name');
$table->string('slug')->unique();
$table->text('intro')->nullable();
$table->string('url_clearnet')->nullable();
$table->string('url_onion')->nullable();
$table->string('url_i2p')->nullable();
$table->string('url_pkdns')->nullable();
$table->string('type');
$table->text('contact')->nullable();
$table->timestamps();
$table->index('created_by');
});
}
public function down(): void
{
Schema::dropIfExists('self_hosted_services');
}
};

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(und :count weiterer Fehler)",
"(and :count more errors)": "(und :count weiterer Fehler)|(und :count weitere Fehler)|(und :count weitere Fehler)",
"2FA Recovery Codes": "2FA-Wiederherstellungscodes",
":count Events erfolgreich erstellt!": "",
"A decryption key is required.": "Ein Entschlüsselungsschlüssel ist nötig.",
"A new verification link has been sent to the email address you provided during registration.": "Ein neuer Bestätigungslink wurde an die E-Mail-Adresse gesendet, die Sie bei der Registrierung angegeben haben.",
"A new verification link has been sent to your email address.": "Ein neuer Bestätigungslink wurde an Ihre E-Mail-Adresse versendet.",
@@ -11,6 +12,7 @@
"Actions": "",
"Aktionen": "",
"Aktiv": "",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "",
"Aktivitäten": "",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "",
@@ -41,6 +43,7 @@
"Bearbeiten": "",
"Beschreibe das Event...": "",
"Beschreibung": "",
"Bist du sicher, dass die Einstellungen korrekt sind?": "",
"Bist du sicher, dass du dieses Event löschen möchtest?": "",
"Bitcoin - Rabbit Hole": "",
"Bitcoin Bildung & Kurse": "",
@@ -80,6 +83,8 @@
"Dashboard": "Dashboard",
"Dashboard - Bitcoin Meetups": "",
"Datum": "",
"Datum des ersten Termins": "",
"Datum des letzten Termins": "",
"Dein Name": "",
"Delete account": "Konto löschen",
"Delete your account and all of its resources": "Löschen Sie Ihr Konto und alle zugehörigen Ressourcen",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "",
"Dozenten erstellen": "",
"Dozentenprofil bearbeiten": "",
"Du bist dabei, mehrere Events zu erstellen.": "",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "",
"Du kannst es jederzeit wieder hinzufügen.": "",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "",
@@ -159,6 +165,7 @@
"Event erstellen": "",
"Event löschen": "",
"Events": "",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "",
@@ -185,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Sollten Sie Schwierigkeiten haben, die Schaltfläche \":actionText\" zu klicken, kopieren Sie den nachfolgenden Link\n in Ihre Adresszeile des Browsers.",
"Inaktiv": "",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "",
"Intervall": "",
"Invalid filename.": "Ungültiger Dateiname.",
"Invalid JSON was returned from the route.": "Von der Route wurde ein ungültiger JSON-Code zurückgegeben.",
"Ist dieser Dozent aktiv?": "",
"Jetzt erstellen": "",
"Kalender-Stream-URL kopieren": "",
"Karte": "",
"Kartenansicht öffnen": "",
@@ -241,6 +250,7 @@
"Login - Bitcoin Meetups": "",
"login using a recovery code": "Mit einem Wiederherstellungscode anmelden",
"login using an authentication code": "Anmelden mit einem Authentifizierungscode",
"Login with lightning ⚡": "",
"Logout": "Abmelden",
"Lokale Buchausleihe für Bitcoin-Meetups.": "",
"Longitude": "",
@@ -265,6 +275,7 @@
"Mehr Informationen": "",
"Meine Meetups": "",
"Meine nächsten Meetup Termine": "",
"Monatlich": "",
"Möchtest du": "",
"Name": "Name",
"Name eingeben": "",
@@ -335,6 +346,8 @@
"Search venues...": "",
"Select a city": "",
"Select a country": "",
"Serientermine erstellen": "",
"Serientermine erstellen?": "",
"Server Error": "Interner Fehler",
"Service Unavailable": "Service nicht verfügbar",
"Settings": "Einstellungen",
@@ -399,6 +412,7 @@
"Two-Factor Auth": "Zwei-Faktor-Authentifizierung",
"Two-Factor Authentication Enabled": "Zwei-Faktor-Authentifizierung aktiviert",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Die Zwei-Faktor-Authentifizierung ist nun aktiviert. Scannen Sie den QR-Code oder geben Sie den Setup-Schlüssel in Ihrer Authentifizierungs-App ein.",
"Uhr": "",
"Uhrzeit": "",
"Um wie viel Uhr beginnt das Event?": "",
"Um wie viel Uhr endet das Event?": "",
@@ -431,6 +445,8 @@
"Vielleicht": "",
"View Recovery Codes": "Wiederherstellungscodes anzeigen",
"Vollständiger Name des Dozenten": "",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "",
"Vorschau der Termine": "",
"Wallpaper": "",
"Wann beginnt das Event?": "",
"Wann dieser Dozent erstellt wurde": "",
@@ -445,6 +461,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "Wenn Sie die Zwei-Faktor-Authentifizierung aktivieren, werden Sie bei der Anmeldung zur Eingabe einer Sicherheits-PIN aufgefordert. Diese PIN können Sie über eine TOTP-unterstützte Anwendung auf Ihrem Smartphone abrufen.",
"Whoops!": "Ups!",
"Wie oft soll das Event wiederholt werden?": "",
"Willkommen": "",
"Willkommen bei Bitcoin Meetups": "",
"Willkommen zurück": "",
@@ -454,6 +471,8 @@
"Wähle deine Sprache aus...": "",
"Wähle deine Zeitzone aus...": "",
"Wähle deine Zeitzone...": "",
"Wähle die Stadt aus...": "",
"Wöchentlich": "",
"You are receiving this email because we received a password reset request for your account.": "Sie erhalten diese E-Mail, weil wir einen Antrag auf eine Zurücksetzung Ihres Passworts bekommen haben.",
"Your email address is unverified.": "Ihre E-Mail-Adresse ist nicht verifiziert.",
"z.B. Berlin": "",
@@ -471,24 +490,5 @@
"Öffnen/RSVP": "",
"Über den Dozenten": "",
"Über den Kurs": "",
"Über uns": "",
"Login with lightning ⚡": "",
"Wähle die Stadt aus...": "",
":count Events erfolgreich erstellt!": "",
"Serientermine erstellen": "",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "",
"Datum des ersten Termins": "",
"Datum des letzten Termins": "",
"Intervall": "",
"Monatlich": "",
"Wöchentlich": "",
"Wie oft soll das Event wiederholt werden?": "",
"Vorschau der Termine": "",
"Uhr": "",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "",
"Serientermine erstellen?": "",
"Du bist dabei, mehrere Events zu erstellen.": "",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "",
"Bist du sicher, dass die Einstellungen korrekt sind?": "",
"Jetzt erstellen": ""
"Über uns": ""
}

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(and :count more error)",
"(and :count more errors)": "(and :count more error)|(and :count more errors)|(and :count more errors)",
"2FA Recovery Codes": "2FA Recovery Codes",
":count Events erfolgreich erstellt!": ":count events successfully created!",
"A decryption key is required.": "A decryption key is required.",
"A new verification link has been sent to the email address you provided during registration.": "A new verification link has been sent to the email address you provided during registration.",
"A new verification link has been sent to your email address.": "A new verification link has been sent to your email address.",
@@ -11,6 +12,7 @@
"Actions": "Actions",
"Aktionen": "Actions",
"Aktiv": "Active",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Enable this option to create multiple events at regular intervals",
"Aktivitäten": "Activities",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Update your profile as a Bitcoin lecturer and your course offerings.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Update your personal information and profile settings.",
@@ -41,6 +43,7 @@
"Bearbeiten": "Edit",
"Beschreibe das Event...": "Describe the event...",
"Beschreibung": "Description",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Are you sure the settings are correct?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Are you sure you want to delete this event?",
"Bitcoin - Rabbit Hole": "Bitcoin - Rabbit Hole",
"Bitcoin Bildung & Kurse": "Bitcoin Education & Courses",
@@ -80,6 +83,8 @@
"Dashboard": "Dashboard",
"Dashboard - Bitcoin Meetups": "Dashboard - Bitcoin Meetups",
"Datum": "Date",
"Datum des ersten Termins": "Date of first event",
"Datum des letzten Termins": "Date of last event",
"Dein Name": "Your name",
"Delete account": "Delete account",
"Delete your account and all of its resources": "Delete your account and all of its resources",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "Create lecturers",
"Dozenten erstellen": "Create lecturer",
"Dozentenprofil bearbeiten": "Edit Lecturer Profile",
"Du bist dabei, mehrere Events zu erstellen.": "You are about to create multiple events.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "You are not logged in and therefore need to type your name yourself.",
"Du kannst es jederzeit wieder hinzufügen.": "You can add it again anytime.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Browse all cities with active Bitcoin Meetups and find events near you.",
@@ -159,6 +165,7 @@
"Event erstellen": "Create event",
"Event löschen": "Delete event",
"Events": "Events",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Incorrectly created events must all be deleted manually.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Find all current Bitcoin Meetups and events in your region.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Find all venues for Bitcoin Meetups and events.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Find Bitcoin Meetups near you with our interactive map.",
@@ -185,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:",
"Inaktiv": "Inactive",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Information about deleting your Bitcoin Meetup account.",
"Intervall": "Interval",
"Invalid filename.": "Invalid filename.",
"Invalid JSON was returned from the route.": "Invalid JSON was returned from the route.",
"Ist dieser Dozent aktiv?": "Is this lecturer active?",
"Jetzt erstellen": "Create now",
"Kalender-Stream-URL kopieren": "Copy calendar stream URL",
"Karte": "Map",
"Kartenansicht öffnen": "Open map view",
@@ -241,6 +250,7 @@
"Login - Bitcoin Meetups": "Login - Bitcoin Meetups",
"login using a recovery code": "login using a recovery code",
"login using an authentication code": "login using an authentication code",
"Login with lightning ⚡": "Login with lightning ⚡",
"Logout": "Logout",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Local book lending for Bitcoin meetups.",
"Longitude": "Longitude",
@@ -265,6 +275,7 @@
"Mehr Informationen": "More information",
"Meine Meetups": "My Meetups",
"Meine nächsten Meetup Termine": "My upcoming meetup dates",
"Monatlich": "Monthly",
"Möchtest du": "Do you want to",
"Name": "Name",
"Name eingeben": "Enter name",
@@ -335,6 +346,8 @@
"Search venues...": "Search venues...",
"Select a city": "Select a city",
"Select a country": "Select a country",
"Serientermine erstellen": "Create recurring events",
"Serientermine erstellen?": "Create recurring events?",
"Server Error": "Server Error",
"Service Unavailable": "Service Unavailable",
"Settings": "Settings",
@@ -399,6 +412,7 @@
"Two-Factor Auth": "Two-Factor Auth",
"Two-Factor Authentication Enabled": "Two-Factor Authentication Enabled",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.",
"Uhr": "o'clock",
"Uhrzeit": "Time",
"Um wie viel Uhr beginnt das Event?": "At what time does the event start?",
"Um wie viel Uhr endet das Event?": "At what time does the event end?",
@@ -431,6 +445,8 @@
"Vielleicht": "Maybe",
"View Recovery Codes": "View Recovery Codes",
"Vollständiger Name des Dozenten": "Full name of the lecturer",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Preview limited to 100 events. More events may be created.",
"Vorschau der Termine": "Preview of events",
"Wallpaper": "Wallpaper",
"Wann beginnt das Event?": "When does the event start?",
"Wann dieser Dozent erstellt wurde": "When this lecturer was created",
@@ -445,6 +461,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Become a Bitcoin lecturer and share your expert knowledge with the community.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.",
"Whoops!": "Whoops!",
"Wie oft soll das Event wiederholt werden?": "How often should the event be repeated?",
"Willkommen": "Welcome",
"Willkommen bei Bitcoin Meetups": "Welcome to Bitcoin Meetups",
"Willkommen zurück": "Welcome back",
@@ -454,6 +471,8 @@
"Wähle deine Sprache aus...": "Choose your language...",
"Wähle deine Zeitzone aus...": "Select your timezone...",
"Wähle deine Zeitzone...": "Choose your timezone...",
"Wähle die Stadt aus...": "Select the city...",
"Wöchentlich": "Weekly",
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account.",
"Your email address is unverified.": "Your email address is unverified.",
"z.B. Berlin": "e.g. Berlin",
@@ -471,24 +490,5 @@
"Öffnen/RSVP": "Open/RSVP",
"Über den Dozenten": "About the lecturer",
"Über den Kurs": "About the course",
"Über uns": "About us",
"Login with lightning ⚡": "Login with lightning ⚡",
"Wähle die Stadt aus...": "Select the city...",
":count Events erfolgreich erstellt!": ":count events successfully created!",
"Serientermine erstellen": "Create recurring events",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Enable this option to create multiple events at regular intervals",
"Datum des ersten Termins": "Date of first event",
"Datum des letzten Termins": "Date of last event",
"Intervall": "Interval",
"Monatlich": "Monthly",
"Wöchentlich": "Weekly",
"Wie oft soll das Event wiederholt werden?": "How often should the event be repeated?",
"Vorschau der Termine": "Preview of events",
"Uhr": "o'clock",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Preview limited to 100 events. More events may be created.",
"Serientermine erstellen?": "Create recurring events?",
"Du bist dabei, mehrere Events zu erstellen.": "You are about to create multiple events.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Incorrectly created events must all be deleted manually.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Are you sure the settings are correct?",
"Jetzt erstellen": "Create now"
}
"Über uns": "About us"
}

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(y :count error más)",
"(and :count more errors)": "(y :count error más)|(y :count errores más)|(y :count errores más)",
"2FA Recovery Codes": "Códigos de recuperación de 2FA",
":count Events erfolgreich erstellt!": "¡:count eventos creados exitosamente!",
"A decryption key is required.": "Se requiere una clave de descifrado.",
"A new verification link has been sent to the email address you provided during registration.": "Se ha enviado un nuevo enlace de verificación a la dirección de correo electrónico que proporcionó durante el registro.",
"A new verification link has been sent to your email address.": "Se ha enviado un nuevo enlace de verificación a su dirección de correo electrónico.",
@@ -11,6 +12,7 @@
"Actions": "Acciones",
"Aktionen": "Acciones",
"Aktiv": "Activo",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Activa esta opción para crear múltiples eventos con intervalos regulares",
"Aktivitäten": "Actividades",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Actualiza tu perfil como profesor de Bitcoin y tus ofertas de cursos.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Actualiza tu información personal y configuración de perfil.",
@@ -41,6 +43,7 @@
"Bearbeiten": "Editar",
"Beschreibe das Event...": "Describe el evento...",
"Beschreibung": "Descripción",
"Bist du sicher, dass die Einstellungen korrekt sind?": "¿Estás seguro de que la configuración es correcta?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "¿Estás seguro de que quieres eliminar este evento?",
"Bitcoin - Rabbit Hole": "Bitcoin - La Madriguera del Conejo",
"Bitcoin Bildung & Kurse": "Educación & Cursos Bitcoin",
@@ -80,6 +83,8 @@
"Dashboard": "Panel",
"Dashboard - Bitcoin Meetups": "Panel - Encuentros Bitcoin",
"Datum": "Fecha",
"Datum des ersten Termins": "Fecha del primer evento",
"Datum des letzten Termins": "Fecha del último evento",
"Dein Name": "Tu nombre",
"Delete account": "Eliminar cuenta",
"Delete your account and all of its resources": "Elimine su cuenta y todos sus recursos",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "Crear profesores",
"Dozenten erstellen": "Crear profesor",
"Dozentenprofil bearbeiten": "Editar perfil de profesor",
"Du bist dabei, mehrere Events zu erstellen.": "Estás a punto de crear múltiples eventos.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "No has iniciado sesión, por lo que debes escribir el nombre tú mismo.",
"Du kannst es jederzeit wieder hinzufügen.": "Puedes volver a añadirlo en cualquier momento.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Busca todas las ciudades con Encuentros Bitcoin activos y encuentra eventos cerca de ti.",
@@ -159,6 +165,7 @@
"Event erstellen": "Crear evento",
"Event löschen": "Eliminar evento",
"Events": "Eventos",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Los eventos creados incorrectamente deberán eliminarse manualmente.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Encuentra todos los Encuentros Bitcoin y eventos actuales en tu región.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Encuentra todos los lugares para Encuentros Bitcoin y eventos.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Encuentra Encuentros Bitcoin cerca de ti con nuestro mapa interactivo.",
@@ -185,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si está teniendo problemas al hacer clic en el botón \":actionText\", copie y pegue la URL de abajo\nen su navegador web:",
"Inaktiv": "Inactivo",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Información sobre cómo eliminar tu cuenta de Encuentros Bitcoin.",
"Intervall": "Intervalo",
"Invalid filename.": "Nombre de archivo no válido.",
"Invalid JSON was returned from the route.": "Se devolvió un JSON no válido desde la ruta.",
"Ist dieser Dozent aktiv?": "¿Está activo este profesor?",
"Jetzt erstellen": "Crear ahora",
"Kalender-Stream-URL kopieren": "Copiar URL del flujo del calendario",
"Karte": "Mapa",
"Kartenansicht öffnen": "Abrir vista de mapa",
@@ -240,6 +249,7 @@
"Login - Bitcoin Meetups": "Iniciar sesión - Encuentros Bitcoin",
"login using a recovery code": "iniciar sesión usando un código de recuperación",
"login using an authentication code": "iniciar sesión usando un código de autenticación",
"Login with lightning ⚡": "Inicia sesión con lightning ⚡",
"Logout": "Finalizar sesión",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Préstamo local de libros para encuentros Bitcoin.",
"Longitude": "Longitud",
@@ -264,6 +274,7 @@
"Mehr Informationen": "Más información",
"Meine Meetups": "Mis encuentros",
"Meine nächsten Meetup Termine": "Mis próximos eventos",
"Monatlich": "Mensual",
"Möchtest du": "¿Quieres",
"Name": "Nombre",
"Name eingeben": "Introducir nombre",
@@ -334,6 +345,8 @@
"Search venues...": "Buscar lugares...",
"Select a city": "Seleccionar ciudad",
"Select a country": "Seleccionar país",
"Serientermine erstellen": "Crear eventos recurrentes",
"Serientermine erstellen?": "¿Crear eventos recurrentes?",
"Server Error": "Error del servidor",
"Service Unavailable": "Servicio no disponible",
"Settings": "Configuración",
@@ -398,6 +411,7 @@
"Two-Factor Auth": "Autenticación de Dos Factores",
"Two-Factor Authentication Enabled": "Autenticación de dos factores habilitada",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "La autenticación de dos factores ya está habilitada. Escanea el código QR o introduce la clave de configuración en tu app de autenticación.",
"Uhr": "hora",
"Uhrzeit": "Hora",
"Um wie viel Uhr beginnt das Event?": "¿A qué hora comienza el evento?",
"Um wie viel Uhr endet das Event?": "¿A qué hora termina el evento?",
@@ -430,6 +444,8 @@
"Vielleicht": "Quizás",
"View Recovery Codes": "Ver códigos de recuperación",
"Vollständiger Name des Dozenten": "Nombre completo del profesor",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Vista previa limitada a 100 eventos. Es posible que se creen más eventos.",
"Vorschau der Termine": "Vista previa de eventos",
"Wallpaper": "Fondo de pantalla",
"Wann beginnt das Event?": "¿Cuándo comienza el evento?",
"Wann dieser Dozent erstellt wurde": "Cuando se creó este profesor",
@@ -444,6 +460,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Conviértete en profesor de Bitcoin y comparte tu conocimiento experto con la comunidad.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "Al activar la autenticación de dos factores, se le solicitará un PIN seguro al iniciar sesión. Puede obtenerlo desde una aplicación compatible con TOTP en su teléfono.",
"Whoops!": "¡Ups!",
"Wie oft soll das Event wiederholt werden?": "¿Con qué frecuencia debe repetirse el evento?",
"Willkommen": "Bienvenido",
"Willkommen bei Bitcoin Meetups": "Bienvenido a Encuentros Bitcoin",
"Willkommen zurück": "Bienvenido de nuevo",
@@ -453,6 +470,8 @@
"Wähle deine Sprache aus...": "Elige tu idioma...",
"Wähle deine Zeitzone aus...": "Selecciona tu zona horaria...",
"Wähle deine Zeitzone...": "Elige tu zona horaria...",
"Wähle die Stadt aus...": "Selecciona la ciudad...",
"Wöchentlich": "Semanal",
"You are receiving this email because we received a password reset request for your account.": "Ha recibido este mensaje porque se solicitó un restablecimiento de contraseña para su cuenta.",
"Your email address is unverified.": "Su dirección de correo electrónico no está verificada.",
"z.B. Berlin": "p.ej. Berlín",
@@ -470,24 +489,5 @@
"Öffnen/RSVP": "Abrir/RSVP",
"Über den Dozenten": "Sobre el profesor",
"Über den Kurs": "Sobre el curso",
"Über uns": "Sobre nosotros",
"Login with lightning ⚡": "Inicia sesión con lightning ⚡",
"Wähle die Stadt aus...": "Selecciona la ciudad...",
":count Events erfolgreich erstellt!": "¡:count eventos creados exitosamente!",
"Serientermine erstellen": "Crear eventos recurrentes",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Activa esta opción para crear múltiples eventos con intervalos regulares",
"Datum des ersten Termins": "Fecha del primer evento",
"Datum des letzten Termins": "Fecha del último evento",
"Intervall": "Intervalo",
"Monatlich": "Mensual",
"Wöchentlich": "Semanal",
"Wie oft soll das Event wiederholt werden?": "¿Con qué frecuencia debe repetirse el evento?",
"Vorschau der Termine": "Vista previa de eventos",
"Uhr": "hora",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Vista previa limitada a 100 eventos. Es posible que se creen más eventos.",
"Serientermine erstellen?": "¿Crear eventos recurrentes?",
"Du bist dabei, mehrere Events zu erstellen.": "Estás a punto de crear múltiples eventos.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Los eventos creados incorrectamente deberán eliminarse manualmente.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "¿Estás seguro de que la configuración es correcta?",
"Jetzt erstellen": "Crear ahora"
}
"Über uns": "Sobre nosotros"
}

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(és még :count hiba)",
"(and :count more errors)": "(és még :count hiba)|(és még :count hiba)|(és még :count hiba)",
"2FA Recovery Codes": "2FA helyreállítási kódok",
":count Events erfolgreich erstellt!": ":count esemény sikeresen létrehozva!",
"A decryption key is required.": "Dekódoló kulcs szükséges.",
"A new verification link has been sent to the email address you provided during registration.": "Egy új ellenőrző linket küldtek a regisztráció során megadott e-mail címre.",
"A new verification link has been sent to your email address.": "Új ellenőrző linket küldtünk az e-mail címére.",
@@ -11,6 +12,7 @@
"Actions": "Műveletek",
"Aktionen": "Műveletek",
"Aktiv": "Aktív",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Aktiváld ezt az opciót több esemény rendszeres időközönkénti létrehozásához",
"Aktivitäten": "Tevékenységek",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Frissítsd Bitcoin oktatói profilodat és kurzuskínálatodat.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Frissítsd személyes adataidat és profilbeállításaidat.",
@@ -41,6 +43,7 @@
"Bearbeiten": "Szerkesztés",
"Beschreibe das Event...": "Írd le az eseményt...",
"Beschreibung": "Leírás",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Biztos vagy benne, hogy a beállítások megfelelőek?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Biztos vagy benne, hogy törölni szeretnéd ezt az eseményt?",
"Bitcoin - Rabbit Hole": "Bitcoin - Nyúlüreg",
"Bitcoin Bildung & Kurse": "Bitcoin Oktatás & Kurzusok",
@@ -80,6 +83,8 @@
"Dashboard": "Műszerfal",
"Dashboard - Bitcoin Meetups": "Irányítópult - Bitcoin Meetupok",
"Datum": "Dátum",
"Datum des ersten Termins": "Az első időpont dátuma",
"Datum des letzten Termins": "Az utolsó időpont dátuma",
"Dein Name": "Neved",
"Delete account": "Fiók törlése",
"Delete your account and all of its resources": "Törölje fiókját és minden erőforrását",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "Oktató létrehozása",
"Dozenten erstellen": "Oktató létrehozása",
"Dozentenprofil bearbeiten": "Oktatói profil szerkesztése",
"Du bist dabei, mehrere Events zu erstellen.": "Több esemény létrehozására készülsz.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "Nem vagy bejelentkezve, ezért be kell írnod a nevedet.",
"Du kannst es jederzeit wieder hinzufügen.": "Bármikor újra hozzáadhatod.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Böngészd az összes várost aktív Bitcoin meetupokkal és találj eseményeket a közeledben.",
@@ -158,6 +164,8 @@
"Event erfolgreich gelöscht!": "Esemény sikeresen törölve!",
"Event erstellen": "Esemény létrehozása",
"Event löschen": "Esemény törlése",
"Events": "Események",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "A hibásan létrehozott időpontokat egyesével kell törölni.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Találd meg az összes aktuális Bitcoin meetupot és eseményt a régiódban.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Találd meg az összes helyszínt Bitcoin meetupokhoz és eseményekhez.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Találj Bitcoin meetupokat a közeledben interaktív térképünkkel.",
@@ -184,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Ha problémákba ütközik a \":actionText\" gombra kattintáskor, másolja be az allábi hivatkozást\na böngészőjébe:",
"Inaktiv": "Inaktív",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Információk Bitcoin meetup fiókod törléséről.",
"Intervall": "Intervallum",
"Invalid filename.": "Érvénytelen fájlnév.",
"Invalid JSON was returned from the route.": "Érvénytelen JSON-t adtak vissza az útvonalról.",
"Ist dieser Dozent aktiv?": "Ez az oktató aktív?",
"Jetzt erstellen": "Létrehozás most",
"Kalender-Stream-URL kopieren": "Naptár stream URL másolása",
"Karte": "Térkép",
"Kartenansicht öffnen": "Térképnézet megnyitása",
@@ -239,6 +249,7 @@
"Login - Bitcoin Meetups": "Bejelentkezés - Bitcoin Meetupok",
"login using a recovery code": "Jelentkezzen be egy helyreállítási kód segítségével",
"login using an authentication code": "Jelentkezzen be egy hitelesítési kód segítségével",
"Login with lightning ⚡": "Bejelentkezés lightning-gal ⚡",
"Logout": "Kijelentkezés",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Helyi könyvkölcsönzés Bitcoin meetupokhoz.",
"Longitude": "Hosszúság",
@@ -263,6 +274,7 @@
"Mehr Informationen": "További információk",
"Meine Meetups": "Meetupjaim",
"Meine nächsten Meetup Termine": "Következő meetup időpontjaim",
"Monatlich": "Havonta",
"Möchtest du": "Szeretnéd",
"Name": "Név",
"Name eingeben": "Név megadása",
@@ -333,6 +345,8 @@
"Search venues...": "Helyszínek keresése...",
"Select a city": "Válassz várost",
"Select a country": "Válassz országot",
"Serientermine erstellen": "Ismétlődő időpontok létrehozása",
"Serientermine erstellen?": "Ismétlődő időpontok létrehozása?",
"Server Error": "Szerver hiba",
"Service Unavailable": "Szolgáltatás nem elérhető",
"Settings": "Beállítások",
@@ -397,6 +411,7 @@
"Two-Factor Auth": "Kéttényezős Auth",
"Two-Factor Authentication Enabled": "A két tényezős hitelesítés engedélyezve",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "A két tényezős hitelesítés már engedélyezve van. Vizsgálja meg a QR -kódot, vagy írja be a Setup gombot az Authenticator alkalmazásba.",
"Uhr": "óra",
"Uhrzeit": "Időpont",
"Um wie viel Uhr beginnt das Event?": "Hány órakor kezdődik az esemény?",
"Um wie viel Uhr endet das Event?": "Hány órakor ér véget az esemény?",
@@ -429,6 +444,8 @@
"Vielleicht": "Talán",
"View Recovery Codes": "A helyreállítási kódok megtekintése",
"Vollständiger Name des Dozenten": "Az oktató teljes neve",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Az előnézet 100 időpontra korlátozódik. Lehet, hogy több időpont lesz létrehozva.",
"Vorschau der Termine": "Időpontok előnézete",
"Wann dieser Dozent erstellt wurde": "Mikor lett ez az oktató létrehozva",
"Wann dieser Kurs erstellt wurde": "Mikor lett ez a kurzus létrehozva",
"Wann dieses Meetup erstellt wurde": "Mikor lett ez a meetup létrehozva",
@@ -439,6 +456,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Válj Bitcoin oktatóvá és oszd meg szakértői tudásodat a közösséggel.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "Ha engedélyezi a két tényezős hitelesítést, a bejelentkezés során a rendszer kéri a biztonságos PIN-kódot. Ez a PIN-kód a telefonján a TOTP által támogatott alkalmazásból származhat.",
"Whoops!": "Hoppá!",
"Wie oft soll das Event wiederholt werden?": "Milyen gyakran ismétlődjön az esemény?",
"Willkommen": "Üdvözlünk",
"Willkommen bei Bitcoin Meetups": "Üdvözlünk a Bitcoin Meetupoknál",
"Willkommen zurück": "Üdv újra",
@@ -448,6 +466,8 @@
"Wähle deine Sprache aus...": "Válaszd ki nyelvedet...",
"Wähle deine Zeitzone aus...": "Válaszd ki időzónádat...",
"Wähle deine Zeitzone...": "Válaszd ki időzónádat...",
"Wähle die Stadt aus...": "Válaszd ki a várost...",
"Wöchentlich": "Hetente",
"You are receiving this email because we received a password reset request for your account.": "Azért kapja ezt az üzenetet, mert a fiókjára jelszó helyreállítási kérés érkezett.",
"Your email address is unverified.": "Az Ön e-mail címe nincs ellenőrizve.",
"z.B. Berlin": "pl. Budapest",
@@ -465,25 +485,5 @@
"Öffnen/RSVP": "Megnyitás/RSVP",
"Über den Dozenten": "Az oktatóról",
"Über den Kurs": "A kurzusról",
"Über uns": "Rólunk",
"Login with lightning ⚡": "Bejelentkezés lightning-gal ⚡",
"Wähle die Stadt aus...": "Válaszd ki a várost...",
":count Events erfolgreich erstellt!": ":count esemény sikeresen létrehozva!",
"Serientermine erstellen": "Ismétlődő időpontok létrehozása",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Aktiváld ezt az opciót több esemény rendszeres időközönkénti létrehozásához",
"Datum des ersten Termins": "Az első időpont dátuma",
"Datum des letzten Termins": "Az utolsó időpont dátuma",
"Intervall": "Intervallum",
"Monatlich": "Havonta",
"Wöchentlich": "Hetente",
"Wie oft soll das Event wiederholt werden?": "Milyen gyakran ismétlődjön az esemény?",
"Vorschau der Termine": "Időpontok előnézete",
"Events": "Események",
"Uhr": "óra",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Az előnézet 100 időpontra korlátozódik. Lehet, hogy több időpont lesz létrehozva.",
"Serientermine erstellen?": "Ismétlődő időpontok létrehozása?",
"Du bist dabei, mehrere Events zu erstellen.": "Több esemény létrehozására készülsz.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "A hibásan létrehozott időpontokat egyesével kell törölni.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Biztos vagy benne, hogy a beállítások megfelelőek?",
"Jetzt erstellen": "Létrehozás most"
}
"Über uns": "Rólunk"
}

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(en :count andere foutmelding)",
"(and :count more errors)": "(en :count andere foutmelding)|(en :count andere foutmeldingen)|(en :count andere foutmeldingen)",
"2FA Recovery Codes": "2FA Recovery Codes",
":count Events erfolgreich erstellt!": ":count evenementen succesvol aangemaakt!",
"A decryption key is required.": "Een decryptiesleutel is verplicht.",
"A new verification link has been sent to the email address you provided during registration.": "Er is een nieuwe verificatielink verstuurd naar het e-mailadres dat je ingegeven hebt tijdens de registratie.",
"A new verification link has been sent to your email address.": "Er is een nieuwe verificatielink naar je e-mailadres verstuurd.",
@@ -11,6 +12,7 @@
"Actions": "Acties",
"Aktionen": "Acties",
"Aktiv": "Actief",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Activeer deze optie om meerdere evenementen met regelmatige intervallen aan te maken",
"Aktivitäten": "Activiteiten",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Werk je profiel als Bitcoin-docent en je cursusaanbiedingen bij.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Werk je persoonlijke informatie en profielinstellingen bij.",
@@ -41,6 +43,7 @@
"Bearbeiten": "Bewerken",
"Beschreibe das Event...": "Beschrijf het evenement...",
"Beschreibung": "Beschrijving",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Weet je zeker dat de instellingen correct zijn?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Weet je zeker dat je dit evenement wilt verwijderen?",
"Bitcoin - Rabbit Hole": "Bitcoin - Rabbit Hole",
"Bitcoin Bildung & Kurse": "Bitcoin Educatie & Cursussen",
@@ -80,6 +83,8 @@
"Dashboard": "Dashboard",
"Dashboard - Bitcoin Meetups": "Dashboard - Bitcoin Meetups",
"Datum": "Datum",
"Datum des ersten Termins": "Datum van de eerste afspraak",
"Datum des letzten Termins": "Datum van de laatste afspraak",
"Dein Name": "Je naam",
"Delete account": "Account verwijderen",
"Delete your account and all of its resources": "Verwijder je account en alle bijbehorende gegevens",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "Docenten aanleggen",
"Dozenten erstellen": "Docenten aanmaken",
"Dozentenprofil bearbeiten": "Docentenprofiel bewerken",
"Du bist dabei, mehrere Events zu erstellen.": "Je staat op het punt meerdere evenementen aan te maken.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "Je bent niet ingelogd en moet daarom de naam zelf intypen.",
"Du kannst es jederzeit wieder hinzufügen.": "Je kunt het altijd weer toevoegen.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Doorzoek alle steden met actieve Bitcoin Meetups en vind evenementen in je buurt.",
@@ -158,6 +164,8 @@
"Event erfolgreich gelöscht!": "Evenement succesvol verwijderd!",
"Event erstellen": "Evenement aanmaken",
"Event löschen": "Evenement verwijderen",
"Events": "Evenementen",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Verkeerd aangemaakte afspraken moeten allemaal handmatig weer worden verwijderd.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Vind alle huidige Bitcoin Meetups en evenementen in je regio.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Vind alle locaties voor Bitcoin Meetups en evenementen.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Vind Bitcoin Meetups in je buurt met onze interactieve kaart.",
@@ -184,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Als je problemen hebt met de \":actionText\" knop, kopieer en plak de URL hieronder\nin je webbrowser:",
"Inaktiv": "Inactief",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Informatie over het verwijderen van je Bitcoin Meetup-account.",
"Intervall": "Interval",
"Invalid filename.": "Ongeldige bestandsnaam.",
"Invalid JSON was returned from the route.": "Er is ongeldige JSON teruggekomen van de route.",
"Ist dieser Dozent aktiv?": "Is deze docent actief?",
"Jetzt erstellen": "Nu aanmaken",
"Kalender-Stream-URL kopieren": "Kalender-stream-URL kopiëren",
"Karte": "Kaart",
"Kartenansicht öffnen": "Kartenweergave openen",
@@ -240,6 +250,7 @@
"Login - Bitcoin Meetups": "Inloggen - Bitcoin Meetups",
"login using a recovery code": "login using a recovery code",
"login using an authentication code": "login using an authentication code",
"Login with lightning ⚡": "Login met lightning ⚡",
"Logout": "Uitloggen",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Lokale boekuitlening voor Bitcoin-Meetups.",
"Longitude": "Lengtegraad",
@@ -264,6 +275,7 @@
"Mehr Informationen": "Meer informatie",
"Meine Meetups": "Mijn Meetups",
"Meine nächsten Meetup Termine": "Mijn volgende Meetup-afspraken",
"Monatlich": "Maandelijks",
"Möchtest du": "Wil je",
"Name": "Naam",
"Name eingeben": "Naam invoeren",
@@ -334,6 +346,8 @@
"Search venues...": "Zoek locaties...",
"Select a city": "Selecteer een stad",
"Select a country": "Selecteer een land",
"Serientermine erstellen": "Serie-afspraken aanmaken",
"Serientermine erstellen?": "Serie-afspraken aanmaken?",
"Server Error": "Serverfout",
"Service Unavailable": "Website onbeschikbaar",
"Settings": "Instellingen",
@@ -399,6 +413,7 @@
"Two-Factor Auth": "Two-Factor Auth",
"Two-Factor Authentication Enabled": "Two-Factor Authentication Enabled",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.",
"Uhr": "uur",
"Uhrzeit": "Tijd",
"Um wie viel Uhr beginnt das Event?": "Hoe laat begint het evenement?",
"Um wie viel Uhr endet das Event?": "Hoe laat eindigt het evenement?",
@@ -431,6 +446,8 @@
"Vielleicht": "Misschien",
"View Recovery Codes": "View Recovery Codes",
"Vollständiger Name des Dozenten": "Volledige naam van de docent",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Voorvertoning beperkt tot 100 afspraken. Er kunnen mogelijk meer afspraken worden aangemaakt.",
"Vorschau der Termine": "Voorvertoning van afspraken",
"Wann dieser Dozent erstellt wurde": "Wanneer deze docent werd aangemaakt",
"Wann dieser Kurs erstellt wurde": "Wanneer deze cursus werd aangemaakt",
"Wann dieses Meetup erstellt wurde": "Wanneer deze Meetup werd aangemaakt",
@@ -441,6 +458,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Word Bitcoin-docent en deel je expertise met de community.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.",
"Whoops!": "Oeps!",
"Wie oft soll das Event wiederholt werden?": "Hoe vaak moet het evenement worden herhaald?",
"Willkommen": "Welkom",
"Willkommen bei Bitcoin Meetups": "Welkom bij Bitcoin Meetups",
"Willkommen zurück": "Welkom terug",
@@ -450,6 +468,8 @@
"Wähle deine Sprache aus...": "Selecteer je taal...",
"Wähle deine Zeitzone aus...": "Selecteer je tijdzone...",
"Wähle deine Zeitzone...": "Selecteer je tijdzone...",
"Wähle die Stadt aus...": "Selecteer de stad...",
"Wöchentlich": "Wekelijks",
"You are receiving this email because we received a password reset request for your account.": "Je ontvangt deze e-mail omdat we een wachtwoordherstel verzoek hebben ontvangen voor je account.",
"Your email address is unverified.": "Je e-mailadres is niet geverifieerd.",
"z.B. Berlin": "bijv. Amsterdam",
@@ -467,25 +487,5 @@
"Öffnen/RSVP": "Openen/RSVP",
"Über den Dozenten": "Over de docent",
"Über den Kurs": "Over de cursus",
"Über uns": "Over ons",
"Login with lightning ⚡": "Login met lightning ⚡",
"Wähle die Stadt aus...": "Selecteer de stad...",
":count Events erfolgreich erstellt!": ":count evenementen succesvol aangemaakt!",
"Serientermine erstellen": "Serie-afspraken aanmaken",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Activeer deze optie om meerdere evenementen met regelmatige intervallen aan te maken",
"Datum des ersten Termins": "Datum van de eerste afspraak",
"Datum des letzten Termins": "Datum van de laatste afspraak",
"Intervall": "Interval",
"Monatlich": "Maandelijks",
"Wöchentlich": "Wekelijks",
"Wie oft soll das Event wiederholt werden?": "Hoe vaak moet het evenement worden herhaald?",
"Vorschau der Termine": "Voorvertoning van afspraken",
"Events": "Evenementen",
"Uhr": "uur",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Voorvertoning beperkt tot 100 afspraken. Er kunnen mogelijk meer afspraken worden aangemaakt.",
"Serientermine erstellen?": "Serie-afspraken aanmaken?",
"Du bist dabei, mehrere Events zu erstellen.": "Je staat op het punt meerdere evenementen aan te maken.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Verkeerd aangemaakte afspraken moeten allemaal handmatig weer worden verwijderd.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Weet je zeker dat de instellingen correct zijn?",
"Jetzt erstellen": "Nu aanmaken"
}
"Über uns": "Over ons"
}

View File

@@ -1,7 +1,8 @@
{
"(and :count more error)": "(i jeszcze :count błąd)",
"(and :count more errors)": "(i jeszcze :count błąd)|(i jeszcze :count błędy)|(i jeszcze :count błędów)",
"2FA Recovery Codes": "Kody odzyskiwania 2FA",
"2FA Recovery Codes": "2FA Recovery Codes",
":count Events erfolgreich erstellt!": ":count wydarzeń zostało pomyślnie utworzonych!",
"A decryption key is required.": "Wymagany jest klucz deszyfrujący.",
"A new verification link has been sent to the email address you provided during registration.": "Nowy link weryfikacyjny został wysłany na adres e-mail podany podczas rejestracji.",
"A new verification link has been sent to your email address.": "Nowy link weryfikacyjny został wysłany na Twój adres e-mail.",
@@ -11,6 +12,7 @@
"Actions": "Akcje",
"Aktionen": "Akcje",
"Aktiv": "Aktywny",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Włącz tę opcję, aby utworzyć wiele wydarzeń w regularnych odstępach czasu",
"Aktivitäten": "Aktywności",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Zaktualizuj swój profil jako wykładowca Bitcoina i swoją ofertę kursów.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Zaktualizuj swoje dane osobowe i ustawienia profilu.",
@@ -35,12 +37,13 @@
"aus deinen Meetups entfernen?": "usunąć z Twoich meetupów?",
"Ausführliche Beschreibung des Kurses": "Szczegółowy opis kursu",
"Ausführliche Beschreibung und Biografie": "Szczegółowy opis i biografia",
"Authentication Code": "Kod uwierzytelniający",
"Back": "Wstecz",
"Authentication Code": "Authentication Code",
"Back": "Back",
"Basic Information": "Podstawowe informacje",
"Bearbeiten": "Edytuj",
"Beschreibe das Event...": "Opisz wydarzenie...",
"Beschreibung": "Opis",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Czy jesteś pewien, że ustawienia są prawidłowe?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Czy na pewno chcesz usunąć to wydarzenie?",
"Bitcoin - Rabbit Hole": "Bitcoin - Królicza Nora",
"Bitcoin Bildung & Kurse": "Bitcoin Edukacja & Kursy",
@@ -61,11 +64,11 @@
"City successfully updated!": "Miasto zostało pomyślnie zaktualizowane!",
"Click here to re-send the verification email.": "Kliknij tutaj, aby ponownie wysłać e-mail weryfikacyjny.",
"Click to connect": "Kliknij, aby połączyć",
"Close": "Zamknij",
"Close": "Close",
"Confirm": "Potwierdź",
"Confirm password": "Potwierdź hasło",
"Confirm Password": "Potwierdź hasło",
"Continue": "Kontynuuj",
"Continue": "Continue",
"Coordinates": "Współrzędne",
"Copied into clipboard": "Skopiowano do schowka",
"Copy": "Kopiuj",
@@ -80,6 +83,8 @@
"Dashboard": "Panel",
"Dashboard - Bitcoin Meetups": "Panel - Bitcoin Meetupy",
"Datum": "Data",
"Datum des ersten Termins": "Data pierwszego terminu",
"Datum des letzten Termins": "Data ostatniego terminu",
"Dein Name": "Twoje imię",
"Delete account": "Usuń konto",
"Delete your account and all of its resources": "Usuń swoje konto i wszystkie powiązane z nim zasoby",
@@ -93,8 +98,8 @@
"Die Endzeit muss nach der Startzeit liegen.": "Czas zakończenia musi być późniejszy niż czas rozpoczęcia.",
"Die nächstgrößte Stadt oder Ort": "Najbliższe większe miasto lub miejscowość",
"Dies ist ein großartiger Überblick über die Bitcoin-Kaninchenhöhle mit Zugängen zu Bereichen, die Bitcoin umfasst. Jedes Thema hat seine eigene Kaninchenhöhle, die durch Infografiken auf einfache und verständliche Weise visualisiert wird, mit QR-Codes, die zu erklärenden Videos und Artikeln führen. Viel Spaß auf Ihrer Entdeckungsreise!": "To świetny przegląd króliczej nory Bitcoina z dostępem do obszarów obejmujących Bitcoin. Każdy temat ma swoją własną króliczą norę, która jest wizualizowana w prosty i zrozumiały sposób za pomocą infografik, z kodami QR prowadzącymi do filmów i artykułów wyjaśniających. Miłej podróży odkrywczej!",
"Disable 2FA": "Wyłącz 2FA",
"Disabled": "Wyłączone",
"Disable 2FA": "Disable 2FA",
"Disabled": "Disabled",
"Diverses": "Różne",
"Documentation": "Dokumentacja",
"Don't have an account?": "Nie masz konta?",
@@ -109,10 +114,11 @@
"Dozenten anlegen": "Utwórz wykładowcę",
"Dozenten erstellen": "Utwórz wykładowcę",
"Dozentenprofil bearbeiten": "Edytuj profil wykładowcy",
"Du bist dabei, mehrere Events zu erstellen.": "Zamierzasz utworzyć wiele wydarzeń.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "Nie jesteś zalogowany, dlatego musisz sam wpisać nazwę.",
"Du kannst es jederzeit wieder hinzufügen.": "Możesz dodać to ponownie w dowolnym momencie.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Przeglądaj wszystkie miasta z aktywnymi Bitcoin Meetupami i znajdź wydarzenia w Twojej okolicy.",
"Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.": "Każdy kod odzyskiwania może być użyty raz, aby uzyskać dostęp do konta i zostanie usunięty po użyciu. Jeśli potrzebujesz więcej, kliknij Wygeneruj ponownie kody powyżej.",
"Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.": "Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.",
"Edit": "Edytuj",
"Edit City": "Edytuj miasto",
"Edit Venue": "Edytuj miejsce",
@@ -122,9 +128,9 @@
"Email Address": "Adres e-mail",
"Email address": "Adres e-mail",
"Email password reset link": "Wyślij link resetujący hasło",
"Enable 2FA": "Włącz 2FA",
"Enable Two-Factor Authentication": "Włącz uwierzytelnianie dwuskładnikowe",
"Enabled": "Włączone",
"Enable 2FA": "Enable 2FA",
"Enable Two-Factor Authentication": "Enable Two-Factor Authentication",
"Enabled": "Enabled",
"Encrypted environment file already exists.": "Zaszyfrowany plik konfiguracji środowiska już istnieje.",
"Encrypted environment file not found.": "Nie znaleziono zaszyfrowanego pliku konfiguracji środowiska.",
"Enddatum": "Data zakończenia",
@@ -133,8 +139,8 @@
"Entdecke Bitcoin Community Events und vernetze dich mit Gleichgesinnten.": "Odkryj wydarzenia społeczności Bitcoin i nawiąż kontakt z osobami o podobnych poglądach.",
"Entdecke die Bitcoin Community in deiner Nähe. Finde lokale Meetups und vernetze dich mit Gleichgesinnten.": "Odkryj społeczność Bitcoin w Twojej okolicy. Znajdź lokalne meetupy i nawiąż kontakt z osobami o podobnych poglądach.",
"Entdecke unsere vielfältigen Bitcoin-Bildungsangebote und Workshops.": "Odkryj nasze różnorodne oferty edukacyjne i warsztaty Bitcoin.",
"Enter the 6-digit code from your authenticator app.": "Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.",
"Enter the authentication code provided by your authenticator application.": "Wprowadź kod uwierzytelniający z aplikacji uwierzytelniającej.",
"Enter the 6-digit code from your authenticator app.": "Enter the 6-digit code from your authenticator app.",
"Enter the authentication code provided by your authenticator application.": "Enter the authentication code provided by your authenticator application.",
"Enter your details below to create your account": "Wprowadź swoje dane poniżej, aby utworzyć konto",
"Enter your email and password below to log in": "Wprowadź swój adres e-mail i hasło poniżej, aby się zalogować",
"Enter your email to receive a password reset link": "Wprowadź swój adres e-mail, aby otrzymać link do resetowania hasła",
@@ -158,6 +164,8 @@
"Event erfolgreich gelöscht!": "Wydarzenie zostało pomyślnie usunięte!",
"Event erstellen": "Utwórz wydarzenie",
"Event löschen": "Usuń wydarzenie",
"Events": "Wydarzenia",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Błędnie utworzone terminy trzeba będzie usunąć ręcznie.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Znajdź wszystkie aktualne Bitcoin Meetupy i wydarzenia w Twoim regionie.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Znajdź wszystkie miejsca dla Bitcoin Meetupów i wydarzeń.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Znajdź Bitcoin Meetupy w Twojej okolicy za pomocą naszej interaktywnej mapy.",
@@ -166,7 +174,7 @@
"Forbidden": "Zabronione",
"Forgot password": "Zapomniane hasło",
"Forgot your password?": "Nie pamiętasz hasła?",
"Full name": "Imię i nazwisko",
"Full name": "Full name",
"Füge eine neue Stadt hinzu, um Bitcoin Meetups in deiner Region zu organisieren.": "Dodaj nowe miasto, aby organizować Bitcoin Meetupy w Twoim regionie.",
"Füge eine neue Stadt zur Datenbank hinzu.": "Dodaj nowe miasto do bazy danych.",
"Füge einen neuen Ort für Bitcoin Meetups und Events hinzu.": "Dodaj nowe miejsce dla Bitcoin Meetupów i wydarzeń.",
@@ -176,7 +184,7 @@
"Go to page :page": "Przejdź do strony :page",
"Grundlegende Informationen": "Podstawowe informacje",
"Hello!": "Cześć!",
"Hide Recovery Codes": "Ukryj kody odzyskiwania",
"Hide Recovery Codes": "Hide Recovery Codes",
"Ich komme": "Przyjdę",
"ID": "ID",
"If you did not create an account, no further action is required.": "Jeśli nie stworzyłeś konta, zignoruj tę wiadomość.",
@@ -184,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Jeżeli masz problemy z kliknięciem przycisku \":actionText\", skopiuj i wklej poniższy adres do pasku przeglądarki:",
"Inaktiv": "Nieaktywny",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Informacje o usuwaniu Twojego konta Bitcoin Meetup.",
"Intervall": "Interwał",
"Invalid filename.": "Nieprawidłowa nazwa pliku.",
"Invalid JSON was returned from the route.": "Routing zwrócił nieprawidłowy kod JSON.",
"Ist dieser Dozent aktiv?": "Czy ten wykładowca jest aktywny?",
"Jetzt erstellen": "Utwórz teraz",
"Kalender-Stream-URL kopieren": "Kopiuj URL strumienia kalendarza",
"Karte": "Mapa",
"Kartenansicht öffnen": "Otwórz widok mapy",
@@ -237,15 +247,16 @@
"Logge dich ein, um auf dein Bitcoin Meetup Konto zuzugreifen und an der Community teilzunehmen.": "Zaloguj się, aby uzyskać dostęp do swojego konta Bitcoin Meetup i dołączyć do społeczności.",
"Login": "Logowanie",
"Login - Bitcoin Meetups": "Logowanie - Bitcoin Meetupy",
"login using a recovery code": "zaloguj się używając kodu odzyskiwania",
"login using an authentication code": "zaloguj się używając kodu uwierzytelniającego",
"login using a recovery code": "login using a recovery code",
"login using an authentication code": "login using an authentication code",
"Login with lightning ⚡": "Zaloguj się przez Lightning ⚡",
"Logout": "Wyloguj",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Lokalna wypożyczalnia książek dla Bitcoin Meetupów.",
"Longitude": "Długość geograficzna",
"Länder mit den meisten Usern": "Kraje z największą liczbą użytkowników",
"Längengrad": "Długość geograficzna",
"Manage your profile and account settings": "Zarządzaj swoim profilem i ustawieniami konta",
"Manage your two-factor authentication settings": "Zarządzaj ustawieniami uwierzytelniania dwuskładnikowego",
"Manage your two-factor authentication settings": "Manage your two-factor authentication settings",
"Matrix": "Matrix",
"Matrix Gruppe": "Grupa Matrix",
"Matrix-Raum Bezeichner oder Link": "Identyfikator pokoju Matrix lub link",
@@ -263,6 +274,7 @@
"Mehr Informationen": "Więcej informacji",
"Meine Meetups": "Moje meetupy",
"Meine nächsten Meetup Termine": "Moje następne terminy meetupów",
"Monatlich": "Miesięcznie",
"Möchtest du": "Czy chcesz",
"Name": "Imię i nazwisko",
"Name eingeben": "Wprowadź nazwę",
@@ -283,48 +295,48 @@
"Nostr": "Nostr",
"Nostr öffentlicher Schlüssel": "Klucz publiczny Nostr",
"Nostr öffentlicher Schlüssel oder Bezeichner": "Klucz publiczny Nostr lub identyfikator",
"Not Found": "Nie znaleziono",
"Not Found": "Nie Znaleziono",
"Nächster Termin": "Następny termin",
"of": "z",
"Offizielle Webseite oder Landingpage": "Oficjalna strona internetowa lub strona docelowa",
"Once your account is deleted, all of its resources and data will also be permanently deleted. Please confirm you would like to permanently delete your account.": "Po usunięciu konta wszystkie jego zasoby i dane zostaną trwale usunięte. Potwierdź, że chcesz trwale usunąć swoje konto.",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "Po usunięciu konta wszystkie jego zasoby i dane zostaną trwale usunięte. Wprowadź hasło, aby potwierdzić trwałe usunięcie konta.",
"or you can": "lub możesz",
"or, enter the code manually": "lub wprowadź kod ręcznie",
"or you can": "or you can",
"or, enter the code manually": "or, enter the code manually",
"Or, return to": "Lub wróć do",
"Ort": "Miejsce",
"Ort erstellen": "Utwórz miejsce",
"Ort hinzufügen": "Dodaj miejsce",
"Orte/Gebiete": "Miejsca/Obszary",
"Page Expired": "Strona wygasła",
"Pagination Navigation": "Nawigacja stron",
"Page Expired": "Strona Wygasła",
"Pagination Navigation": "Nawigacja Stron",
"Passe das Erscheinungsbild deines Bitcoin Meetup Profils an.": "Dostosuj wygląd swojego profilu Bitcoin Meetup.",
"Password": "Hasło",
"Passwort ändern - Bitcoin Meetups": "Zmień hasło - Bitcoin Meetupy",
"Payment Required": "Wymagana płatność",
"Payment Required": "Płatność Wymagana",
"PayNym": "PayNym",
"PayNym für Bitcoin-Zahlungen": "PayNym do płatności Bitcoin",
"Persönliche Webseite oder Portfolio": "Osobista strona internetowa lub portfolio",
"Platform": "Platforma",
"Please click the button below to verify your email address.": "Kliknij poniższy przycisk, aby zweryfikować swój adres e-mail.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Potwierdź dostęp do swojego konta, wprowadzając jeden z kodów odzyskiwania awaryjnego.",
"Please click the button below to verify your email address.": "Kliknij poniższy przycisk aby zweryfikować swój adres e-mail.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Please confirm access to your account by entering one of your emergency recovery codes.",
"Please enter your new password below": "Wprowadź swoje nowe hasło poniżej",
"Please verify your email address by clicking on the link we just emailed to you.": "Zweryfikuj swój adres e-mail, klikając w link, który właśnie wysłaliśmy.",
"Please verify your email address by clicking on the link we just emailed to you.": "Proszę zweryfikować swój adres e-mail, klikając w link, który właśnie wysłaliśmy na Twoją skrzynkę.",
"Population": "Populacja",
"Population Date": "Data populacji",
"Profil bearbeiten - Bitcoin Meetups": "Edytuj profil - Bitcoin Meetupy",
"Profile": "Profil",
"Recovery Code": "Kod odzyskiwania",
"Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "Kody odzyskiwania pozwalają odzyskać dostęp w przypadku utraty urządzenia 2FA. Przechowuj je w bezpiecznym menedżerze has.",
"Recovery Code": "Recovery Code",
"Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.",
"Regards,": "Z poważaniem,",
"Regenerate Codes": "Wygeneruj kody ponownie",
"Regenerate Codes": "Regenerate Codes",
"Register": "Zarejestruj się",
"Remember me": "Zapamiętaj mnie",
"Repository": "Repozytorium",
"Resend verification email": "Wyślij ponownie e-mail weryfikacyjny",
"Reset password": "Zresetuj hasło",
"Reset Password": "Zresetuj hasło",
"Reset Password Notification": "Powiadomienie o resetowaniu hasła",
"Reset Password": "Zresetuj Hasło",
"Reset Password Notification": "Powiadomienie o Zresetowaniu Hasła",
"results": "wyników",
"Save": "Zapisz",
"Saved.": "Zapisano.",
@@ -333,135 +345,143 @@
"Search venues...": "Szukaj miejsc...",
"Select a city": "Wybierz miasto",
"Select a country": "Wybierz kraj",
"Server Error": "Błąd serwera",
"Service Unavailable": "Serwis niedostępny",
"Serientermine erstellen": "Utwórz terminy cykliczne",
"Serientermine erstellen?": "Utworzyć terminy cykliczne?",
"Server Error": "Błąd Serwera",
"Service Unavailable": "Serwis Niedostępny",
"Settings": "Ustawienia",
"Showing": "Wyświetlanie",
"Sign up": "Zarejestruj się",
"Signal": "Signal",
"Verwalte deine Bitcoin Meetups, Events und Einstellungen in deinem persönlichen Dashboard.": "Zarządzaj swoimi Bitcoin Meetupami, wydarzeniami i ustawieniami w swoim osobistym panelu.",
"Willkommen bei Bitcoin Meetups": "Witaj w Bitcoin Meetups",
"Starte deine Bitcoin-Reise und entdecke spannende Inhalte rund um Bitcoin und Blockchain.": "Rozpocznij swoją przygodę z Bitcoinem i odkryj fascynujące treści związane z Bitcoinem i blockchainem.",
"Stadt bearbeiten - Bitcoin Meetups": "Edytuj miasto - Bitcoin Meetupy",
"Städteübersicht - Bitcoin Meetups": "Przegląd miast - Bitcoin Meetupy",
"Verwalte die Termine und Details deiner Bitcoin-Bildungsveranstaltungen.": "Zarządzaj terminami i szczegółami swoich wydarzeń edukacyjnych Bitcoin.",
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Zostań wykładowcą Bitcoin i podziel się swoją wiedzą ekspercką ze społecznością.",
"Ändere dein Passwort für mehr Sicherheit deines Bitcoin Meetup Kontos.": "Zmień swoje hasło, aby zwiększyć bezpieczeństwo swojego konta Bitcoin Meetup.",
"Veranstaltungsort bearbeiten": "Edytuj miejsce wydarzenia",
"Veranstaltungsorte - Übersicht": "Miejsca wydarzeń - Przegląd",
"Willkommen": "Witaj",
"Toximalistisches Infotainment für bullische Bitcoiner.": "Toksymalistyczny infotainment dla byczych Bitcoinerów.",
"Sprache wählen": "Wybierz język",
"Success!": "Sukces!",
"Welt-Karte": "Mapa świata",
"Städte/Gebiete": "Miasta/Regiony",
"Veranstaltungsorte": "Miejsca wydarzeń",
"Sprache wechseln": "Zmień język",
"Zusagen": "Potwierdzenia",
"Vielleicht": "Może",
"Telegram": "Telegram",
"Website": "Strona internetowa",
"Twitter": "Twitter",
"Signal Kontakt- oder Gruppeninformationen": "Informacje kontaktowe lub grupowe Signal",
"Simplex": "Simplex",
"Unauthorized": "Nieautoryzowany",
"Too Many Requests": "Zbyt wiele żądań",
"This is a secure area of the application. Please confirm your password before continuing.": "To jest bezpieczny obszar aplikacji. Potwierdź swoje hasło przed kontynuowaniem.",
"Willkommen zurück": "Witaj ponownie",
"Login with lightning ⚡": "Zaloguj się przez Lightning ⚡",
"Update City": "Zaktualizuj miasto",
"Wähle dein Land...": "Wybierz swój kraj...",
"Suche dein Land...": "Szukaj swojego kraju...",
"Startdatum": "Data rozpoczęcia",
"Startzeit": "Czas rozpoczęcia",
"Um wie viel Uhr beginnt das Event?": "O której godzinie rozpoczyna się wydarzenie?",
"Um wie viel Uhr endet das Event?": "O której godzinie kończy się wydarzenie?",
"Veranstaltungsort": "Miejsce wydarzenia",
"Veranstaltungsort auswählen": "Wybierz miejsce wydarzenia",
"Suche nach Ort...": "Szukaj miejsca...",
"Wo findet das Event statt?": "Gdzie odbywa się wydarzenie?",
"Veranstaltungsort hinzufügen": "Dodaj miejsce wydarzenia",
"z.B. Bitcoin Zentrum München": "np. Bitcoin Centrum Monachium",
"Stadt": "Miasto",
"Stadt auswählen": "Wybierz miasto",
"Suche passende Stadt...": "Szukaj pasującego miasta...",
"Straße": "Ulica",
"z.B. Hauptstraße 1": "np. Główna 1",
"Suche passenden Dozenten...": "Szukaj pasującego wykładowcy...",
"System-generierte ID (nur lesbar)": "ID wygenerowane przez system (tylko do odczytu)",
"Systeminformationen": "Informacje systemowe",
"Unbekannt": "Nieznane",
"Wann dieser Kurs erstellt wurde": "Kiedy ten kurs został utworzony",
"Suche nach Kursen...": "Szukaj kursów...",
"Über den Kurs": "O kursie",
"Über den Dozenten": "O wykładowcy",
"Top Länder": "Top kraje",
"User": "Użytkownicy",
"Top Meetups": "Top meetupy",
"Vollständiger Name des Dozenten": "Pełne imię i nazwisko wykładowcy",
"Untertitel": "Podtytuł",
"Status": "Status",
"Webseite": "Strona internetowa",
"Twitter Benutzername": "Nazwa użytkownika Twitter",
"Twitter-Handle ohne @ Symbol": "Nazwa na Twitterze bez symbolu @",
"Zahlungsinformationen": "Informacje o płatności",
"Wann dieser Dozent erstellt wurde": "Kiedy ten wykładowca został utworzony",
"Suche nach Dozenten...": "Szukaj wykładowców...",
"weitere Termine": "więcej terminów",
"Uhrzeit": "Godzina",
"Um wie viel Uhr startet das Event?": "O której godzinie rozpoczyna się wydarzenie?",
"z.B. Café Mustermann, Hauptstr. 1": "np. Kawiarnia Kowalski, ul. Główna 1",
"Stadt hinzufügen": "Dodaj miasto",
"Soll dieses Meetup auf der Karte angezeigt werden?": "Czy ten meetup ma być wyświetlany na mapie?",
"Telegram Link": "Link do Telegram",
"SimpleX": "SimpleX",
"SimpleX Chat Kontaktinformationen": "Informacje kontaktowe SimpleX Chat",
"Signal Kontakt- oder Gruppeninformationen": "Informacje kontaktowe lub grupowe Signal",
"Zusätzliche Informationen": "Dodatkowe informacje",
"Stadtname": "Nazwa miasta",
"z.B. Berlin": "np. Berlin",
"Soll dieses Meetup auf der Karte angezeigt werden?": "Czy ten meetup ma być wyświetlany na mapie?",
"Sprache wechseln": "Zmień język",
"Sprache wählen": "Wybierz język",
"Stadt": "Miasto",
"Stadt auswählen": "Wybierz miasto",
"Stadt bearbeiten - Bitcoin Meetups": "Edytuj miasto - Bitcoin Meetupy",
"Stadt erstellen": "Utwórz miasto",
"Wann dieses Meetup erstellt wurde": "Kiedy ten meetup został utworzony",
"Suche nach Meetups...": "Szukaj meetupów...",
"Teilnahme": "Uczestnictwo",
"Zurück zum Meetup": "Powrót do meetupu",
"Über uns": "O nas",
"Stadt hinzufügen": "Dodaj miasto",
"Stadtname": "Nazwa miasta",
"Standort": "Lokalizacja",
"Zoom = STRG+Scroll": "Zoom = CTRL+Scroll",
"Öffnen/RSVP": "Otwórz/RSVP",
"Update the appearance settings for your account": "Zaktualizuj ustawienia wyglądu swojego konta",
"System": "System",
"Update password": "Zaktualizuj hasło",
"Update your name and email address": "Zaktualizuj swoje imię i nazwisko oraz adres e-mail",
"Your email address is unverified.": "Twój adres e-mail nie jest zweryfikowany.",
"Zeitzone": "Strefa czasowa",
"Wähle deine Zeitzone aus...": "Wybierz swoją strefę czasową...",
"Zeitzone erfolgreich aktualisiert": "Strefa czasowa pomyślnie zaktualizowana",
"Wähle deine Zeitzone...": "Wybierz swoją strefę czasową...",
"Suche Zeitzone...": "Szukaj strefy czasowej...",
"Venue successfully created!": "Miejsce zostało pomyślnie utworzone!",
"Venue Information": "Informacje o miejscu",
"Startdatum": "Data rozpoczęcia",
"Starte deine Bitcoin-Reise und entdecke spannende Inhalte rund um Bitcoin und Blockchain.": "Rozpocznij swoją przygodę z Bitcoinem i odkryj fascynujące treści związane z Bitcoinem i blockchainem.",
"Startzeit": "Czas rozpoczęcia",
"Status": "Status",
"Straße": "Ulica",
"Street": "Ulica",
"Venue successfully updated!": "Miejsce zostało pomyślnie zaktualizowane!",
"Update Venue": "Zaktualizuj miejsce",
"Venues": "Miejsca",
"Verbinde dich mit Bitcoinern in deiner Nähe": "Połącz się z Bitcoinerami w Twojej okolicy",
"Wähle die Stadt aus...": "Wybierz miasto...",
":count Events erfolgreich erstellt!": ":count wydarzeń zostało pomyślnie utworzonych!",
"Serientermine erstellen": "Utwórz terminy cykliczne",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Włącz tę opcję, aby utworzyć wiele wydarzeń w regularnych odstępach czasu",
"Datum des ersten Termins": "Data pierwszego terminu",
"Datum des letzten Termins": "Data ostatniego terminu",
"Intervall": "Interwał",
"Monatlich": "Miesięcznie",
"Wöchentlich": "Tygodniowo",
"Wie oft soll das Event wiederholt werden?": "Jak często wydarzenie ma być powtarzane?",
"Vorschau der Termine": "Podgląd terminów",
"Events": "Wydarzenia",
"Städte/Gebiete": "Miasta/Regiony",
"Städteübersicht - Bitcoin Meetups": "Przegląd miast - Bitcoin Meetupy",
"Success!": "Sukces!",
"Suche dein Land...": "Szukaj swojego kraju...",
"Suche nach Dozenten...": "Szukaj wykładowców...",
"Suche nach Kursen...": "Szukaj kursów...",
"Suche nach Meetups...": "Szukaj meetupów...",
"Suche nach Ort...": "Szukaj miejsca...",
"Suche passende Stadt...": "Szukaj pasującego miasta...",
"Suche passenden Dozenten...": "Szukaj pasującego wykładowcy...",
"Suche Zeitzone...": "Szukaj strefy czasowej...",
"System": "System",
"System-generierte ID (nur lesbar)": "ID wygenerowane przez system (tylko do odczytu)",
"Systeminformationen": "Informacje systemowe",
"Teilnahme": "Uczestnictwo",
"Telegram": "Telegram",
"Telegram Link": "Link do Telegram",
"The given data was invalid.": "Podane dane były nieprawidłowe.",
"The response is not a streamed response.": "Odpowiedź nie jest odpowiedzią przesyłaną strumieniowo.",
"The response is not a view.": "Odpowiedź nie jest widokiem.",
"This action is unauthorized.": "To działanie jest niedozwolone.",
"This is a secure area of the application. Please confirm your password before continuing.": "To jest bezpieczna strefa aplikacji. Potwierdź swoje hasło przed kontynuowaniem.",
"This password reset link will expire in :count minutes.": "Link do resetowania hasła wygaśnie za :count minut.",
"to": "do",
"To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.": "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.",
"Toggle navigation": "Przełącz nawigację",
"Too Many Requests": "Zbyt Dużo Zapytań",
"Top Länder": "Top kraje",
"Top Meetups": "Top meetupy",
"Toximalistisches Infotainment für bullische Bitcoiner.": "Toksymalistyczny infotainment dla byczych Bitcoinerów.",
"Twitter": "Twitter",
"Twitter Benutzername": "Nazwa użytkownika Twitter",
"Twitter-Handle ohne @ Symbol": "Nazwa na Twitterze bez symbolu @",
"Two Factor Authentication": "Two Factor Authentication",
"Two-Factor Auth": "Two-Factor Auth",
"Two-Factor Authentication Enabled": "Two-Factor Authentication Enabled",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.",
"Uhr": "godz.",
"Uhrzeit": "Godzina",
"Um wie viel Uhr beginnt das Event?": "O której godzinie rozpoczyna się wydarzenie?",
"Um wie viel Uhr endet das Event?": "O której godzinie kończy się wydarzenie?",
"Um wie viel Uhr startet das Event?": "O której godzinie rozpoczyna się wydarzenie?",
"Unauthorized": "Nieautoryzowany Dostęp",
"Unbekannt": "Nieznane",
"Untertitel": "Podtytuł",
"Update City": "Zaktualizuj miasto",
"Update password": "Zaktualizuj hasło",
"Update the appearance settings for your account": "Zaktualizuj ustawienia wyglądu swojego konta",
"Update Venue": "Zaktualizuj miejsce",
"Update your account's appearance settings": "Zaktualizuj ustawienia wyglądu swojego konta",
"Update your name and email address": "Zaktualizuj swoje imię i adres e-mail",
"User": "Użytkownicy",
"Venue Information": "Informacje o miejscu",
"Venue successfully created!": "Miejsce zostało pomyślnie utworzone!",
"Venue successfully updated!": "Miejsce zostało pomyślnie zaktualizowane!",
"Venues": "Miejsca",
"Veranstaltungsort": "Miejsce wydarzenia",
"Veranstaltungsort auswählen": "Wybierz miejsce wydarzenia",
"Veranstaltungsort bearbeiten": "Edytuj miejsce wydarzenia",
"Veranstaltungsort hinzufügen": "Dodaj miejsce wydarzenia",
"Veranstaltungsorte": "Miejsca wydarzeń",
"Veranstaltungsorte - Übersicht": "Miejsca wydarzeń - Przegląd",
"Verbinde dich mit Bitcoinern in deiner Nähe": "Połącz się z Bitcoinerami w Twojej okolicy",
"Verify Authentication Code": "Verify Authentication Code",
"Verify Email Address": "Zweryfikuj Adres E-mail",
"Verwalte deine Bitcoin Meetups, Events und Einstellungen in deinem persönlichen Dashboard.": "Zarządzaj swoimi Bitcoin Meetupami, wydarzeniami i ustawieniami w swoim osobistym panelu.",
"Verwalte die Termine und Details deiner Bitcoin-Bildungsveranstaltungen.": "Zarządzaj terminami i szczegółami swoich wydarzeń edukacyjnych Bitcoin.",
"Vielleicht": "Może",
"View Recovery Codes": "View Recovery Codes",
"Vollständiger Name des Dozenten": "Pełne imię i nazwisko wykładowcy",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Podgląd ograniczony do 100 terminów. Może zostać utworzonych więcej terminów.",
"Serientermine erstellen?": "Utworzyć terminy cykliczne?",
"Du bist dabei, mehrere Events zu erstellen.": "Zamierzasz utworzyć wiele wydarzeń.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Błędnie utworzone terminy trzeba będzie usunąć ręcznie.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Czy jesteś pewien, że ustawienia są prawidłowe?",
"Jetzt erstellen": "Utwórz teraz"
}
"Vorschau der Termine": "Podgląd terminów",
"Wann dieser Dozent erstellt wurde": "Kiedy ten wykładowca został utworzony",
"Wann dieser Kurs erstellt wurde": "Kiedy ten kurs został utworzony",
"Wann dieses Meetup erstellt wurde": "Kiedy ten meetup został utworzony",
"Webseite": "Strona internetowa",
"Website": "Strona internetowa",
"weitere Termine": "więcej terminów",
"Welt-Karte": "Mapa świata",
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Zostań wykładowcą Bitcoin i podziel się swoją wiedzą ekspercką ze społecznością.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.",
"Whoops!": "Ups!",
"Wie oft soll das Event wiederholt werden?": "Jak często wydarzenie ma być powtarzane?",
"Willkommen": "Witaj",
"Willkommen bei Bitcoin Meetups": "Witaj w Bitcoin Meetups",
"Willkommen zurück": "Witaj ponownie",
"With two-factor authentication enabled, you will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.": "With two-factor authentication enabled, you will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.",
"Wo findet das Event statt?": "Gdzie odbywa się wydarzenie?",
"Wähle dein Land...": "Wybierz swój kraj...",
"Wähle deine Zeitzone aus...": "Wybierz swoją strefę czasową...",
"Wähle deine Zeitzone...": "Wybierz swoją strefę czasową...",
"Wähle die Stadt aus...": "Wybierz miasto...",
"Wöchentlich": "Tygodniowo",
"You are receiving this email because we received a password reset request for your account.": "Otrzymujesz ten e-mail, ponieważ otrzymaliśmy prośbę o zresetowanie hasła dla Twojego konta.",
"Your email address is unverified.": "Twój adres e-mail nie został zweryfikowany.",
"z.B. Berlin": "np. Berlin",
"z.B. Bitcoin Zentrum München": "np. Bitcoin Centrum Monachium",
"z.B. Café Mustermann, Hauptstr. 1": "np. Kawiarnia Kowalski, ul. Główna 1",
"z.B. Hauptstraße 1": "np. Główna 1",
"Zahlungsinformationen": "Informacje o płatności",
"Zeitzone": "Strefa czasowa",
"Zeitzone erfolgreich aktualisiert": "Strefa czasowa pomyślnie zaktualizowana",
"Zoom = STRG+Scroll": "Zoom = CTRL+Scroll",
"Zurück zum Meetup": "Powrót do meetupu",
"Zusagen": "Potwierdzenia",
"Zusätzliche Informationen": "Dodatkowe informacje",
"Ändere dein Passwort für mehr Sicherheit deines Bitcoin Meetup Kontos.": "Zmień swoje hasło, aby zwiększyć bezpieczeństwo swojego konta Bitcoin Meetup.",
"Öffnen/RSVP": "Otwórz/RSVP",
"Über den Dozenten": "O wykładowcy",
"Über den Kurs": "O kursie",
"Über uns": "O nas"
}

View File

@@ -2,6 +2,7 @@
"(and :count more error)": "(e mais :count erros)",
"(and :count more errors)": "(e mais :count erros)|(e mais :count erros)|(e mais :count erros)",
"2FA Recovery Codes": "2FA Recovery Codes",
":count Events erfolgreich erstellt!": ":count eventos criados com sucesso!",
"A decryption key is required.": "É necessária uma chave de descriptografia.",
"A new verification link has been sent to the email address you provided during registration.": "Um novo link de verificação foi enviado para o seu endereço de e-mail fornecido durante o registo.",
"A new verification link has been sent to your email address.": "Foi enviado um novo link de verificação para o seu endereço de e-mail.",
@@ -11,6 +12,7 @@
"Actions": "Ações",
"Aktionen": "Ações",
"Aktiv": "Ativo",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Ative esta opção para criar vários eventos com intervalos regulares",
"Aktivitäten": "Atividades",
"Aktualisiere dein Profil als Bitcoin-Dozent und deine Kursangebote.": "Atualize seu perfil como professor de Bitcoin e suas ofertas de cursos.",
"Aktualisiere deine persönlichen Informationen und Profileinstellungen.": "Atualize suas informações pessoais e configurações de perfil.",
@@ -41,6 +43,7 @@
"Bearbeiten": "Editar",
"Beschreibe das Event...": "Descreva o evento...",
"Beschreibung": "Descrição",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Tem certeza de que as configurações estão corretas?",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Tem certeza de que deseja deletar este evento?",
"Bitcoin - Rabbit Hole": "Bitcoin - Toca do Coelho",
"Bitcoin Bildung & Kurse": "Educação & Cursos Bitcoin",
@@ -80,6 +83,8 @@
"Dashboard": "Painel de Controlo",
"Dashboard - Bitcoin Meetups": "Painel - Bitcoin Meetups",
"Datum": "Data",
"Datum des ersten Termins": "Data do primeiro evento",
"Datum des letzten Termins": "Data do último evento",
"Dein Name": "Seu nome",
"Delete account": "Delete account",
"Delete your account and all of its resources": "Delete your account and all of its resources",
@@ -109,6 +114,7 @@
"Dozenten anlegen": "Criar professores",
"Dozenten erstellen": "Criar professores",
"Dozentenprofil bearbeiten": "Editar perfil do professor",
"Du bist dabei, mehrere Events zu erstellen.": "Você está prestes a criar vários eventos.",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "Você não está logado e precisa digitar o nome manualmente.",
"Du kannst es jederzeit wieder hinzufügen.": "Você pode adicioná-lo novamente a qualquer momento.",
"Durchsuche alle Städte mit aktiven Bitcoin Meetups und finde Events in deiner Nähe.": "Pesquise todas as cidades com Bitcoin Meetups ativos e encontre eventos perto de você.",
@@ -158,6 +164,8 @@
"Event erfolgreich gelöscht!": "Evento deletado com sucesso!",
"Event erstellen": "Criar evento",
"Event löschen": "Deletar evento",
"Events": "Eventos",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Eventos criados incorretamente devem ser excluídos manualmente.",
"Finde alle aktuellen Bitcoin Meetups und Events in deiner Region.": "Encontre todos os atuais Bitcoin Meetups e eventos em sua região.",
"Finde alle Veranstaltungsorte für Bitcoin Meetups und Events.": "Encontre todos os locais para Bitcoin Meetups e eventos.",
"Finde Bitcoin Meetups in deiner Nähe mit unserer interaktiven Karte.": "Encontre Bitcoin Meetups perto de você com nosso mapa interativo.",
@@ -184,9 +192,11 @@
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Se não conseguir clicar no botão \":actionText\", copie e cole a URL abaixo\nno seu browser:",
"Inaktiv": "Inativo",
"Informationen zum Löschen deines Bitcoin Meetup Kontos.": "Informações sobre como excluir sua conta de Bitcoin Meetup.",
"Intervall": "Intervalo",
"Invalid filename.": "Nome do ficheiro inválido.",
"Invalid JSON was returned from the route.": "JSON inválido foi retornado da rota.",
"Ist dieser Dozent aktiv?": "Este professor está ativo?",
"Jetzt erstellen": "Criar agora",
"Kalender-Stream-URL kopieren": "Copiar URL do stream de calendário",
"Karte": "Mapa",
"Kartenansicht öffnen": "Abrir vista do mapa",
@@ -239,6 +249,7 @@
"Login - Bitcoin Meetups": "Login - Bitcoin Meetups",
"login using a recovery code": "login using a recovery code",
"login using an authentication code": "login using an authentication code",
"Login with lightning ⚡": "Login com lightning ⚡",
"Logout": "Terminar Sessão",
"Lokale Buchausleihe für Bitcoin-Meetups.": "Empréstimo local de livros para Bitcoin Meetups.",
"Longitude": "Longitude",
@@ -263,6 +274,7 @@
"Mehr Informationen": "Mais informações",
"Meine Meetups": "Meus Meetups",
"Meine nächsten Meetup Termine": "Minhas próximas datas de Meetup",
"Monatlich": "Mensal",
"Möchtest du": "Você deseja",
"Name": "Nome",
"Name eingeben": "Digite o nome",
@@ -333,6 +345,8 @@
"Search venues...": "Buscar locais...",
"Select a city": "Selecionar uma cidade",
"Select a country": "Selecione um país",
"Serientermine erstellen": "Criar eventos recorrentes",
"Serientermine erstellen?": "Criar eventos recorrentes?",
"Server Error": "Erro do servidor",
"Service Unavailable": "Serviço indisponível",
"Settings": "Settings",
@@ -397,6 +411,7 @@
"Two-Factor Auth": "Two-Factor Auth",
"Two-Factor Authentication Enabled": "Two-Factor Authentication Enabled",
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.",
"Uhr": "horas",
"Uhrzeit": "Hora",
"Um wie viel Uhr beginnt das Event?": "A que hora começa o evento?",
"Um wie viel Uhr endet das Event?": "A que hora termina o evento?",
@@ -429,6 +444,8 @@
"Vielleicht": "Talvez",
"View Recovery Codes": "View Recovery Codes",
"Vollständiger Name des Dozenten": "Nome completo do professor",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Prévia limitada a 100 eventos. Mais eventos podem ser criados.",
"Vorschau der Termine": "Prévia dos eventos",
"Wann dieser Dozent erstellt wurde": "Quando este professor foi criado",
"Wann dieser Kurs erstellt wurde": "Quando este curso foi criado",
"Wann dieses Meetup erstellt wurde": "Quando este Meetup foi criado",
@@ -439,6 +456,7 @@
"Werde Bitcoin-Dozent und teile dein Expertenwissen mit der Community.": "Torne-se um professor de Bitcoin e compartilhe seu conhecimento especializado com a comunidade.",
"When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.",
"Whoops!": "Ops!",
"Wie oft soll das Event wiederholt werden?": "Com que frequência o evento deve ser repetido?",
"Willkommen": "Bem-vindo",
"Willkommen bei Bitcoin Meetups": "Bem-vindo aos Bitcoin Meetups",
"Willkommen zurück": "Bem-vindo de volta",
@@ -448,6 +466,8 @@
"Wähle deine Sprache aus...": "Escolha seu idioma...",
"Wähle deine Zeitzone aus...": "Escolha seu fuso horário...",
"Wähle deine Zeitzone...": "Escolha seu fuso horário...",
"Wähle die Stadt aus...": "Selecione a cidade...",
"Wöchentlich": "Semanal",
"You are receiving this email because we received a password reset request for your account.": "Recebeu esse e-mail porque foi solicitada a redefinição da palavra-passe da sua conta.",
"Your email address is unverified.": "Seu endereço de e-mail não foi verificado.",
"z.B. Berlin": "Ex.: Berlin",
@@ -465,25 +485,5 @@
"Öffnen/RSVP": "Abrir/RSVP",
"Über den Dozenten": "Sobre o professor",
"Über den Kurs": "Sobre o curso",
"Über uns": "Sobre nós",
"Login with lightning ⚡": "Login com lightning ⚡",
"Wähle die Stadt aus...": "Selecione a cidade...",
":count Events erfolgreich erstellt!": ":count eventos criados com sucesso!",
"Serientermine erstellen": "Criar eventos recorrentes",
"Aktiviere diese Option, um mehrere Events mit regelmäßigen Abständen zu erstellen": "Ative esta opção para criar vários eventos com intervalos regulares",
"Datum des ersten Termins": "Data do primeiro evento",
"Datum des letzten Termins": "Data do último evento",
"Intervall": "Intervalo",
"Monatlich": "Mensal",
"Wöchentlich": "Semanal",
"Wie oft soll das Event wiederholt werden?": "Com que frequência o evento deve ser repetido?",
"Vorschau der Termine": "Prévia dos eventos",
"Events": "Eventos",
"Uhr": "horas",
"Vorschau auf 100 Termine begrenzt. Es werden möglicherweise mehr Termine erstellt.": "Prévia limitada a 100 eventos. Mais eventos podem ser criados.",
"Serientermine erstellen?": "Criar eventos recorrentes?",
"Du bist dabei, mehrere Events zu erstellen.": "Você está prestes a criar vários eventos.",
"Falsch angelegte Termine müssen alle händisch wieder gelöscht werden.": "Eventos criados incorretamente devem ser excluídos manualmente.",
"Bist du sicher, dass die Einstellungen korrekt sind?": "Tem certeza de que as configurações estão corretas?",
"Jetzt erstellen": "Criar agora"
}
"Über uns": "Sobre nós"
}

View File

@@ -62,6 +62,17 @@
</flux:navlist.item>
</flux:navlist.group>
<flux:navlist.group :heading="__('Community & Dienste')" class="grid">
<flux:navlist.item icon="server" :href="route_with_country('services.index')"
:current="request()->routeIs('services.index')"
wire:navigate
badge="{{ \App\Models\SelfHostedService::query()->count() }}">
<div class="flex items-center space-x-2">
<span>{{ __('Self Hosted Services') }}</span>
</div>
</flux:navlist.item>
</flux:navlist.group>
<flux:navlist.group :heading="__('Kurse')" class="grid">
<flux:navlist.item icon="academic-cap" :href="route_with_country('courses.index')"
:current="request()->routeIs('courses.index')"

View File

@@ -0,0 +1,216 @@
<?php
use App\Attributes\SeoDataAttribute;
use App\Enums\SelfHostedServiceType;
use App\Models\SelfHostedService;
use App\Traits\SeoTrait;
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;
use Livewire\WithFileUploads;
new
#[SeoDataAttribute(key: 'services_create')]
class extends Component {
use WithFileUploads;
use SeoTrait;
#[Validate('image|max:10240')] // 10MB
public $logo;
public string $name = '';
public ?string $intro = null;
public ?string $url_clearnet = null;
public ?string $url_onion = null;
public ?string $url_i2p = null;
public ?string $url_pkdns = null;
public ?string $type = null;
public ?string $contact = null;
public bool $anonymous = false;
protected function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'type' => [
'required', 'in:'.collect(SelfHostedServiceType::cases())->map(fn($c) => $c->value)->implode(',')
],
'intro' => ['nullable', 'string'],
'url_clearnet' => ['nullable', 'url', 'max:255'],
'url_onion' => ['nullable', 'string', 'max:255'],
'url_i2p' => ['nullable', 'string', 'max:255'],
'url_pkdns' => ['nullable', 'string', 'max:255'],
'contact' => ['nullable', 'string'],
'anonymous' => ['boolean'],
];
}
protected function validateAtLeastOneUrl(): void
{
if (empty($this->url_clearnet) && empty($this->url_onion) && empty($this->url_i2p) && empty($this->url_pkdns)) {
$this->addError('url_clearnet', __('Mindestens eine URL muss angegeben werden.'));
throw new \Illuminate\Validation\ValidationException(
validator([], [])
);
}
}
public function save(): void
{
$validated = $this->validate();
$this->validateAtLeastOneUrl();
/** @var SelfHostedService $service */
$service = SelfHostedService::create([
'name' => $validated['name'],
'type' => $validated['type'],
'intro' => $validated['intro'] ?? null,
'url_clearnet' => $validated['url_clearnet'] ?? null,
'url_onion' => $validated['url_onion'] ?? null,
'url_i2p' => $validated['url_i2p'] ?? null,
'url_pkdns' => $validated['url_pkdns'] ?? null,
'contact' => $validated['contact'] ?? null,
'created_by' => $this->anonymous ? null : auth()->id(),
]);
if ($this->logo) {
$service
->addMedia($this->logo->getRealPath())
->usingFileName($this->logo->getClientOriginalName())
->toMediaCollection('logo');
}
session()->flash('status', __('Service erfolgreich erstellt!'));
redirect()->route('services.index', ['country' => request()->route('country')]);
}
public function with(): array
{
return [
'types' => collect(SelfHostedServiceType::cases())->map(fn($c) => [
'value' => $c->value, 'label' => ucfirst($c->value)
]),
];
}
}; ?>
<div class="max-w-4xl mx-auto p-6">
<flux:heading size="xl" class="mb-8">{{ __('Service anlegen') }}</flux:heading>
<form wire:submit="save" class="space-y-10">
<!-- Basic Information -->
<flux:fieldset class="space-y-6">
<flux:legend>{{ __('Grundlegende Informationen') }}</flux:legend>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<flux:file-upload wire:model="logo">
<div class="
relative flex items-center justify-center size-20 rounded transition-colors cursor-pointer
border border-zinc-200 dark:border-white/10 hover:border-zinc-300 dark:hover:border-white/10
bg-zinc-100 hover:bg-zinc-200 dark:bg-white/10 hover:dark:bg-white/15 in-data-dragging:dark:bg-white/15
">
@if($logo)
<img src="{{ $logo?->temporaryUrl() }}" alt="Logo"
class="size-full object-cover rounded"/>
@else
<flux:icon name="cube" variant="solid" class="text-zinc-500 dark:text-zinc-400"/>
@endif
<div class="absolute bottom-0 right-0 bg-white dark:bg-zinc-800 rounded">
<flux:icon name="arrow-up-circle" variant="solid" class="text-zinc-500 dark:text-zinc-400"/>
</div>
</div>
</flux:file-upload>
<flux:field>
<flux:label>{{ __('Name') }} <span class="text-red-500">*</span></flux:label>
<flux:input wire:model="name" placeholder="Name" required/>
<flux:description>{{ __('Der Name des Services') }}</flux:description>
<flux:error name="name"/>
</flux:field>
<flux:field>
<flux:label>{{ __('Typ') }} <span class="text-red-500">*</span></flux:label>
<flux:select wire:model="type" placeholder="{{ __('Bitte wählen') }}" required>
<flux:select.option :value="null"></flux:select.option>
@foreach($types as $t)
<flux:select.option value="{{ $t['value'] }}">{{ $t['label'] }}</flux:select.option>
@endforeach
</flux:select>
<flux:description>{{ __('Art des Services') }}</flux:description>
<flux:error name="type"/>
</flux:field>
<flux:field>
<flux:label>{{ __('Anonym einstellen') }}</flux:label>
<flux:switch wire:model="anonymous"/>
<flux:description>{{ __('Service ohne Autorenangabe einstellen') }}</flux:description>
</flux:field>
</div>
<flux:field>
<flux:label>{{ __('Beschreibung') }}</flux:label>
<flux:textarea rows="4" wire:model="intro"/>
<flux:description>{{ __('Kurze Beschreibung des Services') }}</flux:description>
<flux:error name="intro"/>
</flux:field>
</flux:fieldset>
<!-- URLs -->
<flux:fieldset class="space-y-6">
<flux:legend>{{ __('URLs & Erreichbarkeit') }}</flux:legend>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<flux:field>
<flux:label>{{ __('URL (Clearnet)') }}</flux:label>
<flux:input wire:model="url_clearnet" type="url" placeholder="https://..."/>
<flux:description>{{ __('Normale Web-URL') }}</flux:description>
<flux:error name="url_clearnet"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (Onion/Tor)') }}</flux:label>
<flux:input wire:model="url_onion" placeholder="http://...onion"/>
<flux:description>{{ __('Tor Hidden Service URL') }}</flux:description>
<flux:error name="url_onion"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (I2P)') }}</flux:label>
<flux:input wire:model="url_i2p" placeholder="..."/>
<flux:description>{{ __('I2P Adresse') }}</flux:description>
<flux:error name="url_i2p"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (pkdns)') }}</flux:label>
<flux:input wire:model="url_pkdns" placeholder="..."/>
<flux:description>{{ __('Pkarr DNS Adresse') }}</flux:description>
<flux:error name="url_pkdns"/>
</flux:field>
</div>
<flux:field>
<flux:label>{{ __('Kontaktinformation') }}</flux:label>
<flux:textarea rows="3" wire:model="contact"
placeholder="{{ __('Signal: username, SimpleX: https://..., Email: ...') }}"/>
<flux:description>{{ __('Beliebige Kontaktinformationen (Signal, SimpleX, Email, etc.)') }}</flux:description>
<flux:error name="contact"/>
</flux:field>
</flux:fieldset>
<!-- Form Actions -->
<div class="flex items-center justify-between pt-8 border-t border-gray-200 dark:border-gray-700">
<flux:button class="cursor-pointer" variant="ghost" type="button" onclick="history.back()">
{{ __('Abbrechen') }}
</flux:button>
<flux:button class="cursor-pointer" variant="primary" type="submit">
{{ __('Service erstellen') }}
</flux:button>
</div>
</form>
</div>

View File

@@ -0,0 +1,256 @@
<?php
use App\Attributes\SeoDataAttribute;
use App\Enums\SelfHostedServiceType;
use App\Models\SelfHostedService;
use App\Traits\SeoTrait;
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;
use Livewire\WithFileUploads;
new
#[SeoDataAttribute(key: 'services_edit')]
class extends Component {
use WithFileUploads;
use SeoTrait;
public SelfHostedService $service;
#[Validate('image|max:10240')] // 10MB
public $logo;
public string $name = '';
public ?string $intro = null;
public ?string $url_clearnet = null;
public ?string $url_onion = null;
public ?string $url_i2p = null;
public ?string $url_pkdns = null;
public ?string $type = null;
public ?string $contact = null;
public bool $anonymous = false;
public function mount(): void
{
$this->authorizeAccess();
$this->service->load('media');
$this->name = $this->service->name;
$this->intro = $this->service->intro;
$this->url_clearnet = $this->service->url_clearnet;
$this->url_onion = $this->service->url_onion;
$this->url_i2p = $this->service->url_i2p;
$this->url_pkdns = $this->service->url_pkdns;
$this->type = $this->service->type?->value ?? null;
$this->contact = $this->service->contact;
$this->anonymous = is_null($this->service->created_by);
}
protected function authorizeAccess(): void
{
// Allow edit if user is the creator or if service was created anonymously
if (!is_null($this->service->created_by) && auth()->id() !== $this->service->created_by) {
abort(403);
}
}
protected function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'type' => ['required', 'in:'.collect(SelfHostedServiceType::cases())->map(fn($c) => $c->value)->implode(',')],
'intro' => ['nullable', 'string'],
'url_clearnet' => ['nullable', 'url', 'max:255'],
'url_onion' => ['nullable', 'string', 'max:255'],
'url_i2p' => ['nullable', 'string', 'max:255'],
'url_pkdns' => ['nullable', 'string', 'max:255'],
'contact' => ['nullable', 'string'],
'anonymous' => ['boolean'],
];
}
protected function validateAtLeastOneUrl(): void
{
if (empty($this->url_clearnet) && empty($this->url_onion) && empty($this->url_i2p) && empty($this->url_pkdns)) {
$this->addError('url_clearnet', __('Mindestens eine URL muss angegeben werden.'));
throw new \Illuminate\Validation\ValidationException(
validator([], [])
);
}
}
public function save(): void
{
$this->authorizeAccess();
$validated = $this->validate();
$this->validateAtLeastOneUrl();
$this->service->update([
'name' => $validated['name'],
'type' => $validated['type'],
'intro' => $validated['intro'] ?? null,
'url_clearnet' => $validated['url_clearnet'] ?? null,
'url_onion' => $validated['url_onion'] ?? null,
'url_i2p' => $validated['url_i2p'] ?? null,
'url_pkdns' => $validated['url_pkdns'] ?? null,
'contact' => $validated['contact'] ?? null,
'created_by' => $this->anonymous ? null : ($this->service->created_by ?? auth()->id()),
]);
if ($this->logo) {
$this->service->clearMediaCollection('logo');
$this->service->addMedia($this->logo->getRealPath())
->usingFileName($this->logo->getClientOriginalName())
->toMediaCollection('logo');
$this->logo = null;
$this->service->load('media');
}
session()->flash('status', __('Service erfolgreich aktualisiert!'));
}
public function with(): array
{
return [
'types' => collect(SelfHostedServiceType::cases())->map(fn($c) => ['value' => $c->value, 'label' => ucfirst($c->value)]),
];
}
}; ?>
<div class="max-w-4xl mx-auto p-6">
<flux:heading size="xl" class="mb-8">{{ __('Service bearbeiten') }}: {{ $service->name }}</flux:heading>
<form wire:submit="save" class="space-y-10">
<!-- Basic Information -->
<flux:fieldset class="space-y-6">
<flux:legend>{{ __('Grundlegende Informationen') }}</flux:legend>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<flux:file-upload wire:model="logo">
<div class="
relative flex items-center justify-center size-20 rounded transition-colors cursor-pointer
border border-zinc-200 dark:border-white/10 hover:border-zinc-300 dark:hover:border-white/10
bg-zinc-100 hover:bg-zinc-200 dark:bg-white/10 hover:dark:bg-white/15 in-data-dragging:dark:bg-white/15
">
@if (!$logo && $service->getFirstMedia('logo'))
<img src="{{ $service->getFirstMediaUrl('logo') }}" alt="Logo"
class="size-full object-cover rounded"/>
@elseif($logo)
<img src="{{ $logo?->temporaryUrl() }}" alt="Logo"
class="size-full object-cover rounded"/>
@else
<flux:icon name="cube" variant="solid" class="text-zinc-500 dark:text-zinc-400"/>
@endif
<div class="absolute bottom-0 right-0 bg-white dark:bg-zinc-800 rounded">
<flux:icon name="arrow-up-circle" variant="solid" class="text-zinc-500 dark:text-zinc-400"/>
</div>
</div>
</flux:file-upload>
<flux:field>
<flux:label>{{ __('ID') }}</flux:label>
<flux:input value="{{ $service->id }}" disabled/>
<flux:description>{{ __('System-generierte ID (nur lesbar)') }}</flux:description>
</flux:field>
<flux:field>
<flux:label>{{ __('Name') }} <span class="text-red-500">*</span></flux:label>
<flux:input wire:model="name" required/>
<flux:description>{{ __('Der Name des Services') }}</flux:description>
<flux:error name="name"/>
</flux:field>
<flux:field>
<flux:label>{{ __('Typ') }} <span class="text-red-500">*</span></flux:label>
<flux:select wire:model="type" placeholder="{{ __('Bitte wählen') }}" required>
<flux:select.option :value="null"></flux:select.option>
@foreach($types as $t)
<flux:select.option value="{{ $t['value'] }}">{{ $t['label'] }}</flux:select.option>
@endforeach
</flux:select>
<flux:description>{{ __('Art des Services') }}</flux:description>
<flux:error name="type"/>
</flux:field>
<flux:field>
<flux:label>{{ __('Anonym') }}</flux:label>
<flux:switch wire:model="anonymous"/>
<flux:description>{{ __('Service ohne Autorenangabe') }}</flux:description>
</flux:field>
</div>
<flux:field>
<flux:label>{{ __('Beschreibung') }}</flux:label>
<flux:textarea rows="4" wire:model="intro"/>
<flux:description>{{ __('Kurze Beschreibung des Services') }}</flux:description>
<flux:error name="intro"/>
</flux:field>
</flux:fieldset>
<!-- URLs -->
<flux:fieldset class="space-y-6">
<flux:legend>{{ __('URLs & Erreichbarkeit') }}</flux:legend>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<flux:field>
<flux:label>{{ __('URL (Clearnet)') }}</flux:label>
<flux:input wire:model="url_clearnet" type="url" placeholder="https://..."/>
<flux:description>{{ __('Normale Web-URL') }}</flux:description>
<flux:error name="url_clearnet"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (Onion/Tor)') }}</flux:label>
<flux:input wire:model="url_onion" placeholder="http://...onion"/>
<flux:description>{{ __('Tor Hidden Service URL') }}</flux:description>
<flux:error name="url_onion"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (I2P)') }}</flux:label>
<flux:input wire:model="url_i2p" placeholder="..."/>
<flux:description>{{ __('I2P Adresse') }}</flux:description>
<flux:error name="url_i2p"/>
</flux:field>
<flux:field>
<flux:label>{{ __('URL (pkdns)') }}</flux:label>
<flux:input wire:model="url_pkdns" placeholder="..."/>
<flux:description>{{ __('Pkarr DNS Adresse') }}</flux:description>
<flux:error name="url_pkdns"/>
</flux:field>
</div>
<flux:field>
<flux:label>{{ __('Kontaktinformation') }}</flux:label>
<flux:textarea rows="3" wire:model="contact" placeholder="{{ __('Signal: @username, SimpleX: https://..., Email: ...') }}"/>
<flux:description>{{ __('Beliebige Kontaktinformationen (Signal, SimpleX, Email, etc.)') }}</flux:description>
<flux:error name="contact"/>
</flux:field>
</flux:fieldset>
<!-- Form Actions -->
<div class="flex items-center justify-between pt-8 border-t border-gray-200 dark:border-gray-700">
<flux:button class="cursor-pointer" variant="ghost" type="button" onclick="history.back()">
{{ __('Abbrechen') }}
</flux:button>
<div class="flex items-center gap-4">
@if (session('status'))
<flux:text class="text-green-600 dark:text-green-400 font-medium">
{{ session('status') }}
</flux:text>
@endif
<flux:button class="cursor-pointer" variant="primary" type="submit">
{{ __('Service aktualisieren') }}
</flux:button>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,114 @@
<?php
use App\Attributes\SeoDataAttribute;
use App\Models\SelfHostedService;
use App\Traits\SeoTrait;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new
#[SeoDataAttribute(key: 'services_index')]
class extends Component {
use WithPagination;
use SeoTrait;
public string $country = 'de';
public string $search = '';
public function mount(): void
{
$this->country = request()->route('country', config('app.domain_country'));
}
public function with(): array
{
return [
'services' => SelfHostedService::query()
->when($this->search, fn($q) => $q->where('name', 'ilike', '%'.$this->search.'%'))
->orderBy('name')
->paginate(15),
];
}
}; ?>
<div>
<div class="flex items-center justify-between flex-col md:flex-row mb-6">
<flux:heading size="xl">{{ __('Self Hosted Services') }}</flux:heading>
<div class="flex flex-col md:flex-row items-center gap-4">
<flux:input wire:model.live="search" :placeholder="__('Suche nach Services...')" clearable />
@auth
<flux:button class="cursor-pointer" :href="route_with_country('services.create')" icon="plus" variant="primary">
{{ __('Service erstellen') }}
</flux:button>
@endauth
</div>
</div>
<flux:table :paginate="$services" class="mt-6">
<flux:table.columns>
<flux:table.column>{{ __('Name') }}</flux:table.column>
<flux:table.column>{{ __('Typ') }}</flux:table.column>
<flux:table.column>{{ __('Links') }}</flux:table.column>
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($services as $service)
<flux:table.row :key="$service->id">
<flux:table.cell variant="strong" class="flex items-center gap-3">
<flux:avatar class="[:where(&)]:size-16" :href="route('services.landingpage', ['service' => $service, 'country' => $country])" src="{{ $service->getFirstMedia('logo') ? $service->getFirstMediaUrl('logo', 'thumb') : asset('android-chrome-512x512.png') }}"/>
<a href="{{ route('services.landingpage', ['service' => $service, 'country' => $country]) }}">{{ $service->name }}</a>
</flux:table.cell>
<flux:table.cell>
@if($service->type)
<flux:badge size="sm">{{ ucfirst($service->type->value) }}</flux:badge>
@endif
</flux:table.cell>
<flux:table.cell>
<div class="flex gap-2">
@if($service->url_clearnet)
<flux:link :href="$service->url_clearnet" external variant="subtle" title="Clearnet">
<flux:icon.globe-alt variant="mini" />
</flux:link>
@endif
@if($service->url_onion)
<flux:link :href="$service->url_onion" external variant="subtle" title="Onion">
<flux:icon.lock-closed variant="mini" />
</flux:link>
@endif
@if($service->url_i2p)
<flux:link :href="$service->url_i2p" external variant="subtle" title="I2P">
<flux:icon.link variant="mini" />
</flux:link>
@endif
@if($service->url_pkdns)
<flux:link :href="$service->url_pkdns" external variant="subtle" title="pkdns">
<flux:icon.link variant="mini" />
</flux:link>
@endif
@if($service->contact_url)
<flux:link :href="$service->contact_url" external variant="subtle" title="Kontakt">
<flux:icon.envelope variant="mini" />
</flux:link>
@endif
</div>
</flux:table.cell>
<flux:table.cell>
@auth
@if(auth()->id() === $service->created_by)
<flux:button :href="route_with_country('services.edit', ['service' => $service])" size="xs" variant="filled" icon="pencil">
{{ __('Bearbeiten') }}
</flux:button>
@endif
@else
<flux:link :href="route('login')">{{ __('Log in') }}</flux:link>
@endauth
</flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
</div>

View File

@@ -0,0 +1,96 @@
<?php
use App\Attributes\SeoDataAttribute;
use App\Models\SelfHostedService;
use App\Traits\SeoTrait;
use Livewire\Volt\Component;
new
#[SeoDataAttribute(key: 'services_landingpage')]
class extends Component {
use SeoTrait;
public SelfHostedService $service;
public $country = 'de';
public function mount(): void
{
$this->country = request()->route('country', config('app.domain_country'));
}
public function with(): array
{
return [
'service' => $this->service,
];
}
}; ?>
<div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2 space-y-6">
<div class="flex items-center gap-4">
<flux:avatar class="[:where(&)]:size-24 [:where(&)]:text-base" size="xl"
src="{{ $service->getFirstMediaUrl('logo') }}"/>
<div>
<flux:heading size="xl" class="mb-1">{{ $service->name }}</flux:heading>
@if($service->type)
<flux:badge size="sm">{{ ucfirst($service->type->value) }}</flux:badge>
@endif
</div>
</div>
@if($service->intro)
<div>
<flux:heading size="lg" class="mb-2">{{ __('Über den Service') }}</flux:heading>
<x-markdown class="prose whitespace-pre-wrap">{!! $service->intro !!}</x-markdown>
</div>
@endif
</div>
<div class="space-y-4">
<flux:heading size="lg">{{ __('Links') }}</flux:heading>
<div class="grid grid-cols-1 gap-2">
@if($service->url_clearnet)
<flux:button href="{{ $service->url_clearnet }}" target="_blank" variant="ghost" class="justify-start">
<flux:icon.globe-alt class="w-5 h-5 mr-2" />
Clearnet
</flux:button>
@endif
@if($service->url_onion)
<flux:button href="{{ $service->url_onion }}" target="_blank" variant="ghost" class="justify-start">
<flux:icon.lock-closed class="w-5 h-5 mr-2" />
Onion / Tor
</flux:button>
@endif
@if($service->url_i2p)
<flux:button href="{{ $service->url_i2p }}" target="_blank" variant="ghost" class="justify-start">
<flux:icon.link class="w-5 h-5 mr-2" />
I2P
</flux:button>
@endif
@if($service->url_pkdns)
<flux:button href="{{ $service->url_pkdns }}" target="_blank" variant="ghost" class="justify-start">
<flux:icon.link class="w-5 h-5 mr-2" />
pkdns
</flux:button>
@endif
@if($service->contact_url)
<flux:button href="{{ $service->contact_url }}" target="_blank" variant="ghost" class="justify-start">
<flux:icon.envelope class="w-5 h-5 mr-2" />
{{ __('Kontakt') }}
</flux:button>
@endif
</div>
@auth
@if(auth()->id() === $service->created_by)
<flux:button :href="route_with_country('services.edit', ['service' => $service])" variant="primary" icon="pencil">
{{ __('Bearbeiten') }}
</flux:button>
@endif
@endauth
</div>
</div>
</div>

View File

@@ -96,6 +96,10 @@ Route::middleware([])
Volt::route('cities', 'cities.index')->name('cities.index');
Volt::route('venues', 'venues.index')->name('venues.index');
// Self Hosted Services public routes
Volt::route('services', 'services.index')->name('services.index');
Volt::route('service/{service:slug}', 'services.landingpage')->name('services.landingpage');
});
Route::middleware(['auth'])
@@ -120,6 +124,10 @@ Route::middleware(['auth'])
Volt::route('venue-create', 'venues.create')->name('venues.create');
Volt::route('venue-edit/{venue}', 'venues.edit')->name('venues.edit');
// Self Hosted Services protected routes
Volt::route('service-create', 'services.create')->name('services.create');
Volt::route('service-edit/{service}', 'services.edit')->name('services.edit');
Route::redirect('settings', 'settings/profile');
Volt::route('settings/profile', 'settings.profile')->name('settings.profile');

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
use App\Enums\SelfHostedServiceType;
use App\Models\SelfHostedService;
use App\Models\User;
use Livewire\Volt\Volt;
it('creates a self hosted service', function () {
$user = User::factory()->create();
$component = Volt::test('services.create')
->actingAs($user)
->set('name', 'My Node')
->set('type', SelfHostedServiceType::Mempool->value)
->set('url_clearnet', 'https://example.com')
->set('contact_url', 'https://contact.example.com')
->call('save');
expect(SelfHostedService::where('name', 'My Node')->exists())->toBeTrue();
});

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use App\Models\SelfHostedService;
use Livewire\Volt\Volt;
it('renders services index', function () {
SelfHostedService::factory()->count(2)->create();
$component = Volt::test('services.index');
$component->assertStatus(200)
->assertSee('Self Hosted Services');
});