Compare commits

...

38 Commits

Author SHA1 Message Date
Chamika J
7f20353674 Merge pull request #220 from JayaruPerera/feature/project-activity-logs
feat: Add project activity logs API and frontend components
2025-07-11 11:27:52 +05:30
jayaruperera
bef69da0d1 feat: Add project activity logs API and frontend components
- Implemented a new API route for fetching project activity logs.
- Created a service for handling API requests related to project activity logs.
- Developed Redux slice for managing activity log state.
- Added components for displaying activity logs, including a virtualized list for performance.
- Implemented filtering options for activity logs.
- Added PDF export functionality for activity logs.
- Integrated activity log view into the project view with appropriate UI elements.
2025-07-02 10:36:35 +05:30
Chamika J
f80ec9797e Merge pull request #216 from Dev-Tanaay/bug/Documentation
Solved the documentation error with issue number
2025-07-02 09:09:27 +05:30
Dev-Tanaay
fbbd820512 Updation in Documentation 2025-07-01 17:29:02 +05:30
Chamika J
5d0777f67c Merge pull request #183 from Worklenz/fix/added-i18n-alb-and-de
Update LANGUAGE_TYPE enum to include 'alb' and 'de' for Albanian and …
2025-06-24 22:39:29 +05:30
chamiakJ
f1d504f985 Update LANGUAGE_TYPE enum to include 'alb' and 'de' for Albanian and German languages 2025-06-24 22:28:33 +05:30
Chamika J
6a4d77d904 Merge pull request #182 from Worklenz/feature/german-i18n
Feature/german i18n
2025-06-24 21:47:45 +05:30
Chamika J
c35d53266a Merge pull request #179 from OminduHirushka/language/de
German Language
2025-06-24 21:47:06 +05:30
Omindu Hirushka
dc096f5e12 german language switch 2025-06-24 14:17:23 +05:30
Omindu Hirushka
a681aadcfa german language 2025-06-24 14:17:08 +05:30
Chamika J
f15f3f5110 Merge pull request #176 from Worklenz/feature/albanian-i18n
Feature/albanian i18n
2025-06-23 07:24:12 +05:30
Chamika J
07ae71fd23 Merge pull request #168 from OminduHirushka/upstream/language/alb
albanian language
2025-06-23 07:23:42 +05:30
Chamika J
26270b2842 Merge pull request #166 from kithmina1999/chore/docker-image-size-reduction
Chore/docker image size reduction
2025-06-23 06:56:14 +05:30
kithmina1999
0d0596b767 ci(docker): update postgres healthcheck and retry settings
- Modify healthcheck command to use DB_NAME and DB_USER environment variables
- Reduce retries from 10 to 5 to fail faster if database is unavailable
2025-06-20 08:48:51 +05:30
kithmina1999
eca7af2d6f chore: remove obsolete git file with process information
The file contained outdated process information that was no longer relevant to the project. This cleanup helps maintain a cleaner codebase.
2025-06-19 22:32:17 +05:30
kithmina1999
3ace14fcdb build(docker): optimize docker setup and compose configuration
- Split Dockerfile into multi-stage build to reduce final image size
- Update docker-compose.yml with restart policies and health checks
- Improve .dockerignore files for both frontend and backend
- Fix MinIO bucket creation script to use 'alias set' instead of deprecated command
- Enhance PostgreSQL healthcheck configuration
2025-06-19 22:26:20 +05:30
Omindu Hirushka
99bec6c7f9 albanian language switch 2025-06-18 17:04:58 +05:30
Omindu Hirushka
ef299f1f4a albanian language 2025-06-17 16:03:08 +05:30
Chamika J
66b0709e6e Merge pull request #154 from Worklenz/chore/added-google-analytics
Chore/added google analytics
2025-06-12 10:10:00 +05:30
chamiakJ
a2ed33214d Merge branch 'main' of https://github.com/Worklenz/worklenz into chore/added-google-analytics 2025-06-12 09:49:32 +05:30
Chamika J
a3dccd690d Merge pull request #141 from gdevenyi/patch-1
Fix use of deprecated mc command for bucket creation
2025-06-12 09:46:45 +05:30
chamiakJ
69313fba34 Enhance privacy notice functionality in index.html
- Updated the privacy notice button to use an ID for easier event handling.
- Added an event listener to the button to manage the display of the notice and store user consent in localStorage.
2025-06-12 09:38:24 +05:30
chamiakJ
1889c58598 Refactor privacy notice implementation in index.html
- Introduced a dedicated function to display the privacy notice for users of the open source version.
- Updated the styling and content of the privacy notice for improved visibility and clarity.
- Added a DOMContentLoaded event listener to conditionally show the notice based on the environment and previous interactions.
2025-06-12 09:34:53 +05:30
chamiakJ
e9f0162439 Enhance documentation and integrate Google Analytics
- Added an Analytics section to the README.md, detailing what is tracked, privacy measures, and opt-out instructions.
- Implemented Google Analytics in index.html, including a privacy notice for open source users and environment-specific tracking IDs.
- Updated worklenz-frontend README.md to include a License section.
2025-06-12 09:26:00 +05:30
Chamika J
323b17185c Merge pull request #148 from kithmina1999/fix/postgres-password-auth
fix: change DB_PASSWORD to static value
2025-06-05 13:18:41 +05:30
kithmina1999
09f44a5685 fix: change DB_PASSWORD to static value for development
Using a static password simplifies development environment setup. The previous random password generation caused issues during local testing and debugging.
2025-06-05 10:40:06 +05:30
Chamika J
f4ab7841fb Merge pull request #105 from MRNafisiA/main
increase the memory limit to prevent crashing during build time.
2025-05-30 10:15:47 +05:30
Chamika J
3de4f69a62 Merge pull request #142 from gdevenyi/patch-2
Generate random passwords in update-docker-env.sh
2025-05-30 09:42:46 +05:30
Gabriel A. Devenyi
102be2c24a Generate random passwords in update-docker-env.sh 2025-05-29 15:56:56 -04:00
Chamika J
3a39b25e64 Merge pull request #144 from kithmina1999/docs/add-video-guides
Update README.md to include video guides for local and remote deployment
2025-05-28 13:01:16 +05:30
kithmina1999
32248f8424 Update README.md to include video guides for local and remote deployment
- Added a section for a video guide on local Docker deployment.
- Included a video guide for deploying Worklenz to a remote server.
2025-05-28 09:32:32 +05:30
Gabriel A. Devenyi
a1f8776743 Fix use of deprecated mc command for bucket creation 2025-05-27 22:32:36 -04:00
Chamika J
7e431d645a Merge pull request #134 from Worklenz/chamikaJ-patch-1
Update README.md
2025-05-21 08:28:32 +05:30
Chamika J
cef4bffd69 Update README.md
updated logo URL
2025-05-21 08:28:09 +05:30
Chamika J
51767ebbdb Merge pull request #115 from kithmina1999/fix/minio-createbuckets-entrypoint
correct MinIO createbuckets entrypoint script syntax
2025-05-09 15:41:02 +05:30
kithmina1999
ad91148616 Add Google OAuth variables to Docker environment setup script 2025-05-09 15:34:27 +05:30
kithmina1999
38df66044d Fix entrypoint syntax for MinIO bucket creation script 2025-05-09 15:29:36 +05:30
MRNafisiA
75391641fd increase the memory limit to prevent crashing during build time. 2025-05-02 15:53:48 +03:30
155 changed files with 5049 additions and 181 deletions

View File

@@ -1,6 +1,6 @@
<h1 align="center">
<a href="https://worklenz.com" target="_blank" rel="noopener noreferrer">
<img src="https://app.worklenz.com/assets/icons/icon-144x144.png" alt="Worklenz Logo" width="75">
<img src="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/icon-144x144.png" alt="Worklenz Logo" width="75">
</a>
<br>
Worklenz
@@ -69,8 +69,7 @@ cd worklenz
2. Set up environment variables
- Copy the example environment files
```bash
cp .env.example .env
cp worklenz-backend/.env.example worklenz-backend/.env
cp worklenz-backend/.env.template worklenz-backend/.env
```
- Update the environment variables with your configuration
@@ -192,6 +191,27 @@ Email [info@worklenz.com](mailto:info@worklenz.com) to disclose any security vul
This project is licensed under the [MIT License](LICENSE).
## Analytics
Worklenz uses Google Analytics to understand how the application is being used. This helps us improve the application and make better decisions about future development.
### What We Track
- Anonymous usage statistics
- Page views and navigation patterns
- Feature usage
- Browser and device information
### Privacy
- Analytics is opt-in only
- No personal information is collected
- Users can opt-out at any time
- Data is stored according to Google's privacy policy
### How to Opt-Out
If you've previously opted in and want to opt-out:
1. Clear your browser's local storage for the Worklenz domain
2. Or click the "Decline" button in the analytics notice if it appears
## Screenshots
<p align="center">
@@ -315,6 +335,7 @@ docker-compose up -d
docker-compose down
```
## MinIO Integration
The project uses MinIO as an S3-compatible object storage service, which provides an open-source alternative to AWS S3 for development and production.
@@ -403,6 +424,10 @@ This script generates properly configured environment files for both development
- Frontend: http://localhost:5000
- Backend API: http://localhost:3000 (or https://localhost:3000 with SSL)
4. Video Guide
For a visual walkthrough of the local Docker deployment process, check out our [step-by-step video guide](https://www.youtube.com/watch?v=AfwAKxJbqLg).
### Remote Server Deployment
When deploying to a remote server:
@@ -428,6 +453,10 @@ When deploying to a remote server:
- Frontend: http://your-server-hostname:5000
- Backend API: http://your-server-hostname:3000
4. Video Guide
For a complete walkthrough of deploying Worklenz to a remote server, check out our [deployment video guide](https://www.youtube.com/watch?v=CAZGu2iOXQs&t=10s).
### Environment Configuration
The Docker setup uses environment variables to configure the services:

View File

@@ -7,15 +7,17 @@ services:
ports:
- "5000:5000"
depends_on:
backend:
condition: service_started
- backend
restart: unless-stopped
env_file:
- ./worklenz-frontend/.env.production
networks:
- worklenz
backend:
image: docker.io/chamikajaycey/worklenz-backend:latest
build:
context: ./worklenz-backend
dockerfile: Dockerfile
container_name: worklenz_backend
ports:
- "3000:3000"
@@ -24,6 +26,7 @@ services:
condition: service_healthy
minio:
condition: service_started
restart: unless-stopped
env_file:
- ./worklenz-backend/.env
networks:
@@ -35,6 +38,7 @@ services:
ports:
- "9000:9000"
- "9001:9001"
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-minioadmin}
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-minioadmin}
@@ -50,29 +54,27 @@ services:
container_name: worklenz_createbuckets
depends_on:
- minio
restart: on-failure
entrypoint: >
/bin/sh -c "
# Wait for MinIO to be available
echo 'Waiting for MinIO to start...'
sleep 15;
# Retry up to 5 times
for i in 1 2 3 4 5; do
echo \"Attempt $$i to connect to MinIO...\"
if /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; then
echo \"Successfully connected to MinIO!\"
/usr/bin/mc mb --ignore-existing myminio/worklenz-bucket;
/usr/bin/mc policy set public myminio/worklenz-bucket;
exit 0;
fi
echo \"Connection failed, retrying in 5 seconds...\"
sleep 5;
done
echo \"Failed to connect to MinIO after 5 attempts\"
exit 1;
"
/bin/sh -c '
echo "Waiting for MinIO to start...";
sleep 15;
for i in 1 2 3 4 5; do
echo "Attempt $i to connect to MinIO...";
if /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; then
echo "Successfully connected to MinIO!";
/usr/bin/mc mb --ignore-existing myminio/worklenz-bucket;
/usr/bin/mc policy set public myminio/worklenz-bucket;
exit 0;
fi
echo "Connection failed, retrying in 5 seconds...";
sleep 5;
done;
echo "Failed to connect to MinIO after 5 attempts";
exit 1;
'
networks:
- worklenz
db:
image: postgres:15
container_name: worklenz_db
@@ -85,6 +87,7 @@ services:
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- worklenz
volumes:
@@ -94,22 +97,19 @@ services:
target: /docker-entrypoint-initdb.d
consistency: cached
command: >
bash -c '
if command -v apt-get >/dev/null 2>&1; then
bash -c ' if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi &&
find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '\''
fi && find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '\''
dos2unix "{}" 2>/dev/null || true
chmod +x "{}"
'\'' \; &&
exec docker-entrypoint.sh postgres
'
'\'' \; && exec docker-entrypoint.sh postgres '
volumes:
worklenz_postgres_data:
worklenz_minio_data:
networks:
worklenz:

View File

@@ -73,13 +73,21 @@ cat > worklenz-backend/.env << EOL
NODE_ENV=production
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET=change_me_in_production
COOKIE_SECRET=change_me_in_production
SESSION_SECRET=$(openssl rand -base64 48)
COOKIE_SECRET=$(openssl rand -base64 48)
# CORS
SOCKET_IO_CORS=${FRONTEND_URL}
SERVER_CORS=${FRONTEND_URL}
# Google Login
GOOGLE_CLIENT_ID="your_google_client_id"
GOOGLE_CLIENT_SECRET="your_google_client_secret"
GOOGLE_CALLBACK_URL="${FRONTEND_URL}/secure/google/verify"
LOGIN_FAILURE_REDIRECT="${FRONTEND_URL}/auth/authenticating"
LOGIN_SUCCESS_REDIRECT="${FRONTEND_URL}/auth/authenticating"
# Database
DB_HOST=db
DB_PORT=5432
@@ -115,7 +123,7 @@ SLACK_WEBHOOK=
COMMIT_BUILD_IMMEDIATELY=true
# JWT Secret
JWT_SECRET=change_me_in_production
JWT_SECRET=$(openssl rand -base64 48)
EOL
echo "Environment configuration updated for ${HOSTNAME} with" $([ "$USE_SSL" = "true" ] && echo "HTTPS/WSS" || echo "HTTP/WS")
@@ -130,4 +138,4 @@ echo "Frontend URL: ${FRONTEND_URL}"
echo "API URL: ${HTTP_PREFIX}${HOSTNAME}:3000"
echo "Socket URL: ${WS_PREFIX}${HOSTNAME}:3000"
echo "MinIO Dashboard URL: ${MINIO_DASHBOARD_URL}"
echo "CORS is configured to allow requests from: ${FRONTEND_URL}"
echo "CORS is configured to allow requests from: ${FRONTEND_URL}"

View File

@@ -1,5 +1,9 @@
node_modules
npm-debug.log
build
.scannerwork
coverage
.dockerignore
.git
*.md
tests

View File

@@ -1,26 +1,39 @@
# Use the official Node.js 20 image as a base
FROM node:20
# --- Stage 1: Build ---
FROM node:20-slim AS builder
ARG RELEASE_VERSION
RUN apt-get update && apt-get install -y \
python3 \
make \
g++ \
curl \
postgresql-server-dev-all \
&& rm -rf /var/lib/apt/lists/*
# Create and set the working directory
WORKDIR /usr/src/app
# Install global dependencies
RUN npm install -g ts-node typescript grunt grunt-cli
# Copy package.json and package-lock.json (if available)
COPY package*.json ./
# Install app dependencies
RUN npm ci
# Copy the rest of the application code
COPY . .
# Run the build script to compile TypeScript to JavaScript
RUN npm run build
# Expose the port the app runs on
EXPOSE 3000
RUN echo "$RELEASE_VERSION" > release
# --- Stage 2: Production Image ---
FROM node:20-slim
RUN apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /usr/src/app/package*.json ./
COPY --from=builder /usr/src/app/build ./build
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/release ./release
COPY --from=builder /usr/src/app/worklenz-email-templates ./worklenz-email-templates
EXPOSE 3000
CMD ["node", "build/bin/www"]
# Start the application
CMD ["npm", "start"]

View File

@@ -12,7 +12,7 @@ CREATE TYPE DEPENDENCY_TYPE AS ENUM ('blocked_by');
CREATE TYPE SCHEDULE_TYPE AS ENUM ('daily', 'weekly', 'yearly', 'monthly', 'every_x_days', 'every_x_weeks', 'every_x_months');
CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt');
CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt', 'alb', 'de');
-- START: Users
CREATE SEQUENCE IF NOT EXISTS users_user_no_seq START 1;

View File

@@ -1,18 +1,17 @@
import moment from "moment";
import {IWorkLenzRequest} from "../interfaces/worklenz-request";
import {IWorkLenzResponse} from "../interfaces/worklenz-response";
import Excel from "exceljs";
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import db from "../config/db";
import {ServerResponse} from "../models/server-response";
import { ServerResponse } from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
import {formatDuration, formatLogText, getColor} from "../shared/utils";
import { formatDuration, formatLogText, getColor } from "../shared/utils";
export default class ActivitylogsController extends WorklenzControllerBase {
@HandleExceptions()
public static async get(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const {id} = req.params;
const { id } = req.params;
const q = `SELECT get_activity_logs_by_task($1) AS activity_logs;`;
const result = await db.query(q, [id]);
const [data] = result.rows;
@@ -31,4 +30,196 @@ export default class ActivitylogsController extends WorklenzControllerBase {
return res.status(200).send(new ServerResponse(true, data.activity_logs));
}
}
@HandleExceptions()
public static async getByProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
try {
console.log("Received request for project activity logs:", req.params, req.query);
const projectId = req.params.id;
const page = parseInt(req.query.page as string) || 1;
const size = parseInt(req.query.size as string) || 20;
const offset = (page - 1) * size;
const filterType = req.query.filter as string || "all";
// Add filter conditions
let filterClause = "";
const filterParams = [projectId];
let paramIndex = 2;
if (filterType && filterType !== "all") {
filterClause = ` AND tal.attribute_type = $${paramIndex}`;
filterParams.push(filterType);
paramIndex++;
}
// Defensive UUID regex for safe casting
const uuidRegex = "'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'";
const q = `
SELECT
tal.id,
tal.task_id,
tal.attribute_type,
tal.log_type,
tal.old_value,
tal.new_value,
tal.prev_string,
tal.next_string,
tal.created_at,
-- Task details
(SELECT name FROM tasks WHERE id = tal.task_id) AS task_name,
(SELECT task_no FROM tasks WHERE id = tal.task_id) AS task_no,
CONCAT((SELECT key FROM projects WHERE id = $1), '-', (SELECT task_no FROM tasks WHERE id = tal.task_id)) AS task_key,
-- User details
(SELECT ROW_TO_JSON(user_data) FROM (
SELECT
u.id,
u.name,
u.avatar_url,
u.email
FROM users u
WHERE u.id = tal.user_id
) user_data) AS done_by,
-- Status details for status changes (safe UUID cast)
CASE
WHEN tal.attribute_type = 'status' AND tal.old_value ~ ${uuidRegex} THEN
(SELECT ROW_TO_JSON(status_data) FROM (
SELECT
ts.name
FROM task_statuses ts
WHERE ts.id = tal.old_value::UUID
) status_data)
ELSE NULL
END AS previous_status,
CASE
WHEN tal.attribute_type = 'status' AND tal.new_value ~ ${uuidRegex} THEN
(SELECT ROW_TO_JSON(status_data) FROM (
SELECT
ts.name
FROM task_statuses ts
WHERE ts.id = tal.new_value::UUID
) status_data)
ELSE NULL
END AS next_status,
-- Priority details for priority changes (safe UUID cast)
CASE
WHEN tal.attribute_type = 'priority' AND tal.old_value ~ ${uuidRegex} THEN
(SELECT ROW_TO_JSON(priority_data) FROM (
SELECT
tp.name
FROM task_priorities tp
WHERE tp.id = tal.old_value::UUID
) priority_data)
ELSE NULL
END AS previous_priority,
CASE
WHEN tal.attribute_type = 'priority' AND tal.new_value ~ ${uuidRegex} THEN
(SELECT ROW_TO_JSON(priority_data) FROM (
SELECT
tp.name
FROM task_priorities tp
WHERE tp.id = tal.new_value::UUID
) priority_data)
ELSE NULL
END AS next_priority,
-- Assigned user details for assignee changes (safe UUID cast)
CASE
WHEN tal.attribute_type = 'assignee' AND tal.new_value ~ ${uuidRegex} THEN
(SELECT ROW_TO_JSON(user_data) FROM (
SELECT
u.id,
u.name,
u.avatar_url,
u.email
FROM users u
WHERE u.id = tal.new_value::UUID
) user_data)
ELSE NULL
END AS assigned_user
FROM task_activity_logs tal
WHERE tal.project_id = $1${filterClause}
ORDER BY tal.created_at DESC
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`;
const countQuery = `
SELECT COUNT(*) as total
FROM task_activity_logs
WHERE project_id = $1${filterClause}
`;
const [result, countResult] = await Promise.all([
db.query(q, [...filterParams, size, offset]),
db.query(countQuery, filterType && filterType !== "all" ? [projectId, filterType] : [projectId])
]);
const total = parseInt(countResult.rows[0]?.total || "0");
// Format the logs
for (const log of result.rows) {
if (log.attribute_type === "estimation") {
log.previous = formatDuration(moment.duration(log.old_value, "minutes"));
log.current = formatDuration(moment.duration(log.new_value, "minutes"));
} else {
log.previous = log.old_value;
log.current = log.new_value;
}
// Add color to users
if (log.assigned_user) {
log.assigned_user.color_code = getColor(log.assigned_user.name);
}
if (log.done_by) {
log.done_by.color_code = getColor(log.done_by.name);
}
// Add default colors for status and priority since table doesn't have color_code
if (log.previous_status) {
log.previous_status.color_code = "#d9d9d9"; // Default gray color
}
if (log.next_status) {
log.next_status.color_code = "#1890ff"; // Default blue color
}
if (log.previous_priority) {
log.previous_priority.color_code = "#d9d9d9"; // Default gray color
}
if (log.next_priority) {
log.next_priority.color_code = "#ff4d4f"; // Default red color for priority
}
// Generate log text
log.log_text = await formatLogText(log);
log.attribute_type = log.attribute_type?.replace(/_/g, " ");
}
const response = {
logs: result.rows,
pagination: {
current: page,
pageSize: size,
total,
totalPages: Math.ceil(total / size)
}
};
return res.status(200).send(new ServerResponse(true, response));
} catch (error: any) {
console.error("❌ Error in getByProjectId:", error);
return res.status(500).send(new ServerResponse(false, null, `Internal server error: ${error.message}`));
}
}
@HandleExceptions()
public static async exportProjectActivityLogs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<void> {
// ...keep your export logic as is...
}
}

View File

@@ -0,0 +1,291 @@
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import moment from "moment";
import db from "../config/db";
import { ServerResponse } from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
// --- Helpers -------------------------------------------------------------
function formatDuration(duration: moment.Duration | null): string {
if (!duration) return "0m";
const hours = Math.floor(duration.asHours());
const minutes = duration.minutes();
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
function generateLogText(attributeType: string): string {
const map: Record<string,string> = {
name: "updated task name",
status: "changed status",
priority: "changed priority",
assignee: "updated assignee",
end_date: "changed due date",
start_date: "changed start date",
estimation: "updated time estimation",
description: "updated description",
phase: "changed phase",
labels: "updated labels",
};
return map[attributeType] || "made changes to";
}
function isValidUuid(id?: string): boolean {
return !!id && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
}
// Get a consistent color for a user based on their name
function getColorFromName(name: string): string {
if (!name) return "#1890ff";
const colors = [
"#f56a00", "#7265e6", "#ffbf00", "#00a2ae",
"#1890ff", "#52c41a", "#eb2f96", "#faad14",
"#722ed1", "#13c2c2", "#fa8c16", "#a0d911"
];
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = name.charCodeAt(i) + ((hash << 5) - hash);
}
return colors[Math.abs(hash) % colors.length];
}
// --- Controller ----------------------------------------------------------
export default class ProjectActivityLogsController extends WorklenzControllerBase {
@HandleExceptions()
public static async getByProjectId(
req: IWorkLenzRequest,
res: IWorkLenzResponse
): Promise<IWorkLenzResponse> {
// 1) Extract & validate inputs
const projectId = req.params.id;
if (!isValidUuid(projectId)) {
return res
.status(400)
.json({ done: false, body: null, error: "Invalid project ID." });
}
const page = parseInt(req.query.page as string, 10) || 1;
const size = parseInt(req.query.size as string, 10) || 20;
const offset = (page - 1) * size;
const filterType = (req.query.filter as string) || "all";
const allowedFilters = [
"all","name","status","priority","assignee",
"end_date","start_date","estimation","description","phase"
];
if (!allowedFilters.includes(filterType)) {
return res
.status(400)
.json({ done: false, body: null, error: "Invalid filter type." });
}
// 2) Build parameterized SQL
let mainQuery = `
SELECT
tal.id,
tal.task_id,
tal.user_id,
tal.attribute_type,
tal.log_type,
tal.old_value,
tal.new_value,
tal.prev_string,
tal.next_string,
tal.created_at,
t.name AS task_name,
t.task_no AS task_no,
p.key AS project_key,
-- Include user details directly
u.id AS user_id,
u.name AS user_name,
u.email AS user_email,
u.avatar_url AS user_avatar_url
FROM task_activity_logs tal
LEFT JOIN tasks t ON tal.task_id = t.id
LEFT JOIN projects p ON tal.project_id = p.id
LEFT JOIN users u ON tal.user_id = u.id
WHERE tal.project_id = $1
`;
const queryParams: any[] = [projectId];
if (filterType !== "all") {
mainQuery += ` AND tal.attribute_type = $2`;
queryParams.push(filterType);
}
mainQuery += ` ORDER BY tal.created_at DESC`;
// placeholders for LIMIT / OFFSET
const limitIdx = queryParams.length + 1;
const offsetIdx = queryParams.length + 2;
mainQuery += ` LIMIT $${limitIdx} OFFSET $${offsetIdx}`;
queryParams.push(size, offset);
// Count query
const countQuery = `
SELECT COUNT(*) AS total
FROM task_activity_logs tal
WHERE tal.project_id = $1
`;
const countParams = filterType !== "all"
? [projectId, filterType]
: [projectId];
try {
// 3) Execute SQL
const [dataResult, countResult] = await Promise.all([
db.query(mainQuery, queryParams),
db.query(
countQuery + (filterType !== "all" ? ` AND tal.attribute_type = $2` : ""),
countParams
),
]);
const total = parseInt(countResult.rows[0]?.total || "0", 10);
// 4) Transform rows
const rows = dataResult.rows;
const logs = await Promise.all(rows.map(async (r: any) => {
const log: any = { ...r };
// Correctly structure user information
log.done_by = {
id: r.user_id || "",
name: r.user_name || "Unknown User",
avatar_url: r.user_avatar_url,
email: r.user_email || "",
color_code: r.user_name ? getColorFromName(r.user_name) : "#1890ff"
};
// task key
log.task_key = r.project_key && r.task_no ?
`${r.project_key}-${r.task_no}` :
`TASK-${r.task_id?.substring(0, 8) || "unknown"}`;
// duration / estimation formatting
if (log.attribute_type === "estimation") {
const oldMin = parseInt(log.old_value, 10);
const newMin = parseInt(log.new_value, 10);
log.previous = !isNaN(oldMin)
? formatDuration(moment.duration(oldMin, "minutes"))
: log.old_value;
log.current = !isNaN(newMin)
? formatDuration(moment.duration(newMin, "minutes"))
: log.new_value;
} else {
log.previous = log.old_value;
log.current = log.new_value;
}
// humanfriendly action
log.log_text = generateLogText(r.attribute_type);
// Handle status changes
if (log.attribute_type === "status" && log.old_value && isValidUuid(log.old_value)) {
try {
const prevStatus = await db.query(
`SELECT name, color_code FROM task_statuses WHERE id = $1`,
[log.old_value]
);
if (prevStatus.rows.length > 0) {
log.previous_status = {
name: prevStatus.rows[0].name,
color_code: prevStatus.rows[0].color_code || "#d9d9d9"
};
}
} catch (err) {
console.error("Error fetching previous status:", err);
}
}
if (log.attribute_type === "status" && log.new_value && isValidUuid(log.new_value)) {
try {
const nextStatus = await db.query(
`SELECT name, color_code FROM task_statuses WHERE id = $1`,
[log.new_value]
);
if (nextStatus.rows.length > 0) {
log.next_status = {
name: nextStatus.rows[0].name,
color_code: nextStatus.rows[0].color_code || "#1890ff"
};
}
} catch (err) {
console.error("Error fetching next status:", err);
}
}
// Handle priority changes
if (log.attribute_type === "priority" && log.old_value && isValidUuid(log.old_value)) {
try {
const prevPriority = await db.query(
`SELECT name, color_code FROM task_priorities WHERE id = $1`,
[log.old_value]
);
if (prevPriority.rows.length > 0) {
log.previous_priority = {
name: prevPriority.rows[0].name,
color_code: prevPriority.rows[0].color_code || "#d9d9d9"
};
}
} catch (err) {
console.error("Error fetching previous priority:", err);
}
}
if (log.attribute_type === "priority" && log.new_value && isValidUuid(log.new_value)) {
try {
const nextPriority = await db.query(
`SELECT name, color_code FROM task_priorities WHERE id = $1`,
[log.new_value]
);
if (nextPriority.rows.length > 0) {
log.next_priority = {
name: nextPriority.rows[0].name,
color_code: nextPriority.rows[0].color_code || "#ff4d4f"
};
}
} catch (err) {
console.error("Error fetching next priority:", err);
}
}
// Handle assignee changes
if (log.attribute_type === "assignee" && log.new_value && isValidUuid(log.new_value)) {
try {
const assignedUser = await db.query(
`SELECT id, name, avatar_url, email FROM users WHERE id = $1`,
[log.new_value]
);
if (assignedUser.rows.length > 0) {
log.assigned_user = {
...assignedUser.rows[0],
color_code: getColorFromName(assignedUser.rows[0].name)
};
}
} catch (err) {
console.error("Error fetching assigned user:", err);
}
}
return log;
}));
// 5) Send back a clean response
const response = {
logs,
pagination: {
current: page,
pageSize: size,
total,
totalPages: Math.ceil(total / size),
}
};
return res.status(200).send(new ServerResponse(true, response));
} catch (err) {
console.error("🔥 getByProjectId error:", err);
return res.status(500).send(
new ServerResponse(false, null, "Internal server error fetching logs.")
);
}
}
}

View File

@@ -7,5 +7,7 @@ import safeControllerFunction from "../../shared/safe-controller-function";
const activityLogsApiRouter = express.Router();
activityLogsApiRouter.get("/:id", idParamValidator, safeControllerFunction(ActivitylogsController.get));
activityLogsApiRouter.get("/project/:id", idParamValidator, safeControllerFunction(ActivitylogsController.getByProjectId));
activityLogsApiRouter.get("/project/:id/export", safeControllerFunction(ActivitylogsController.exportProjectActivityLogs));
export default activityLogsApiRouter;

View File

@@ -1,120 +1,126 @@
import express from "express";
import AccessControlsController from "../../controllers/access-controls-controller";
import AuthController from "../../controllers/auth-controller";
import LogsController from "../../controllers/logs-controller";
import OverviewController from "../../controllers/overview-controller";
import TaskPrioritiesController from "../../controllers/task-priorities-controller";
import attachmentsApiRouter from "./attachments-api-router";
import clientsApiRouter from "./clients-api-router";
import jobTitlesApiRouter from "./job-titles-api-router";
import notificationsApiRouter from "./notifications-api-router";
import personalOverviewApiRouter from "./personal-overview-api-router";
import projectMembersApiRouter from "./project-members-api-router";
import projectsApiRouter from "./projects-api-router";
import settingsApiRouter from "./settings-api-router";
import statusesApiRouter from "./statuses-api-router";
import subTasksApiRouter from "./sub-tasks-api-router";
import taskCommentsApiRouter from "./task-comments-api-router";
import taskWorkLogApiRouter from "./task-work-log-api-router";
import tasksApiRouter from "./tasks-api-router";
import teamMembersApiRouter from "./team-members-api-router";
import teamsApiRouter from "./teams-api-router";
import timezonesApiRouter from "./timezones-api-router";
import todoListApiRouter from "./todo-list-api-router";
import projectStatusesApiRouter from "./project-statuses-api-router";
import labelsApiRouter from "./labels-api-router";
import sharedProjectsApiRouter from "./shared-projects-api-router";
import resourceAllocationApiRouter from "./resource-allocation-api-router";
import taskTemplatesApiRouter from "./task-templates-api-router";
import projectInsightsApiRouter from "./project-insights-api-router";
import passwordValidator from "../../middlewares/validators/password-validator";
import adminCenterApiRouter from "./admin-center-api-router";
import reportingApiRouter from "./reporting-api-router";
import activityLogsApiRouter from "./activity-logs-api-router";
import safeControllerFunction from "../../shared/safe-controller-function";
import projectFoldersApiRouter from "./project-folders-api-router";
import taskPhasesApiRouter from "./task-phases-api-router";
import projectCategoriesApiRouter from "./project-categories-api-router";
import homePageApiRouter from "./home-page-api-router";
import ganttApiRouter from "./gantt-api-router";
import projectCommentsApiRouter from "./project-comments-api-router";
import reportingExportApiRouter from "./reporting-export-api-router";
import projectHealthsApiRouter from "./project-healths-api-router";
import ptTasksApiRouter from "./pt-tasks-api-router";
import projectTemplatesApiRouter from "./project-templates-api";
import ptTaskPhasesApiRouter from "./pt_task-phases-api-router";
import ptStatusesApiRouter from "./pt-statuses-api-router";
import workloadApiRouter from "./gannt-apis/workload-api-router";
import roadmapApiRouter from "./gannt-apis/roadmap-api-router";
import scheduleApiRouter from "./gannt-apis/schedule-api-router";
import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router";
import projectManagerApiRouter from "./project-managers-api-router";
import billingApiRouter from "./billing-api-router";
import taskDependenciesApiRouter from "./task-dependencies-api-router";
import taskRecurringApiRouter from "./task-recurring-api-router";
import express from "express";
import AccessControlsController from "../../controllers/access-controls-controller";
import AuthController from "../../controllers/auth-controller";
import LogsController from "../../controllers/logs-controller";
import OverviewController from "../../controllers/overview-controller";
import TaskPrioritiesController from "../../controllers/task-priorities-controller";
import attachmentsApiRouter from "./attachments-api-router";
import clientsApiRouter from "./clients-api-router";
import jobTitlesApiRouter from "./job-titles-api-router";
import notificationsApiRouter from "./notifications-api-router";
import personalOverviewApiRouter from "./personal-overview-api-router";
import projectMembersApiRouter from "./project-members-api-router";
import projectsApiRouter from "./projects-api-router";
import settingsApiRouter from "./settings-api-router";
import statusesApiRouter from "./statuses-api-router";
import subTasksApiRouter from "./sub-tasks-api-router";
import taskCommentsApiRouter from "./task-comments-api-router";
import taskWorkLogApiRouter from "./task-work-log-api-router";
import tasksApiRouter from "./tasks-api-router";
import teamMembersApiRouter from "./team-members-api-router";
import teamsApiRouter from "./teams-api-router";
import timezonesApiRouter from "./timezones-api-router";
import todoListApiRouter from "./todo-list-api-router";
import projectStatusesApiRouter from "./project-statuses-api-router";
import labelsApiRouter from "./labels-api-router";
import sharedProjectsApiRouter from "./shared-projects-api-router";
import resourceAllocationApiRouter from "./resource-allocation-api-router";
import taskTemplatesApiRouter from "./task-templates-api-router";
import projectInsightsApiRouter from "./project-insights-api-router";
import passwordValidator from "../../middlewares/validators/password-validator";
import adminCenterApiRouter from "./admin-center-api-router";
import reportingApiRouter from "./reporting-api-router";
import activityLogsApiRouter from "./activity-logs-api-router";
import safeControllerFunction from "../../shared/safe-controller-function";
import projectFoldersApiRouter from "./project-folders-api-router";
import taskPhasesApiRouter from "./task-phases-api-router";
import projectCategoriesApiRouter from "./project-categories-api-router";
import homePageApiRouter from "./home-page-api-router";
import ganttApiRouter from "./gantt-api-router";
import projectCommentsApiRouter from "./project-comments-api-router";
import reportingExportApiRouter from "./reporting-export-api-router";
import projectHealthsApiRouter from "./project-healths-api-router";
import ptTasksApiRouter from "./pt-tasks-api-router";
import projectTemplatesApiRouter from "./project-templates-api";
import ptTaskPhasesApiRouter from "./pt_task-phases-api-router";
import ptStatusesApiRouter from "./pt-statuses-api-router";
import workloadApiRouter from "./gannt-apis/workload-api-router";
import roadmapApiRouter from "./gannt-apis/roadmap-api-router";
import scheduleApiRouter from "./gannt-apis/schedule-api-router";
import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router";
import projectManagerApiRouter from "./project-managers-api-router";
import billingApiRouter from "./billing-api-router";
import taskDependenciesApiRouter from "./task-dependencies-api-router";
import taskRecurringApiRouter from "./task-recurring-api-router";
import customColumnsApiRouter from "./custom-columns-api-router";
import projectActivityLogsApiRouter from "./project-activity-logs-api-router";
const api = express.Router();
api.use("/projects", projectsApiRouter);
api.use("/team-members", teamMembersApiRouter);
api.use("/job-titles", jobTitlesApiRouter);
api.use("/clients", clientsApiRouter);
api.use("/teams", teamsApiRouter);
api.use("/tasks", tasksApiRouter);
api.use("/settings", settingsApiRouter);
api.use("/personal-overview", personalOverviewApiRouter);
api.use("/statuses", statusesApiRouter);
api.use("/todo-list", todoListApiRouter);
api.use("/notifications", notificationsApiRouter);
api.use("/attachments", attachmentsApiRouter);
api.use("/sub-tasks", subTasksApiRouter);
api.use("/project-members", projectMembersApiRouter);
api.use("/task-time-log", taskWorkLogApiRouter);
api.use("/task-comments", taskCommentsApiRouter);
api.use("/timezones", timezonesApiRouter);
api.use("/project-statuses", projectStatusesApiRouter);
api.use("/labels", labelsApiRouter);
api.use("/resource-allocation", resourceAllocationApiRouter);
api.use("/shared/projects", sharedProjectsApiRouter);
api.use("/task-templates", taskTemplatesApiRouter);
api.use("/project-insights", projectInsightsApiRouter);
api.use("/admin-center", adminCenterApiRouter);
api.use("/reporting", reportingApiRouter);
api.use("/activity-logs", activityLogsApiRouter);
api.use("/projects-folders", projectFoldersApiRouter);
api.use("/task-phases", taskPhasesApiRouter);
api.use("/project-categories", projectCategoriesApiRouter);
api.use("/home", homePageApiRouter);
api.use("/gantt", ganttApiRouter);
api.use("/project-comments", projectCommentsApiRouter);
api.use("/reporting-export", reportingExportApiRouter);
api.use("/project-healths", projectHealthsApiRouter);
api.use("/project-templates", projectTemplatesApiRouter);
api.use("/pt-tasks", ptTasksApiRouter);
api.use("/pt-task-phases", ptTaskPhasesApiRouter);
api.use("/pt-statuses", ptStatusesApiRouter);
api.use("/workload-gannt", workloadApiRouter);
api.use("/roadmap-gannt", roadmapApiRouter);
api.use("/schedule-gannt", scheduleApiRouter);
api.use("/schedule-gannt-v2", scheduleApiV2Router);
api.use("/project-managers", projectManagerApiRouter);
api.get("/overview/:id", safeControllerFunction(OverviewController.getById));
api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get));
api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword));
api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles));
api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog));
api.use("/billing", billingApiRouter);
api.use("/task-dependencies", taskDependenciesApiRouter);
api.use("/task-recurring", taskRecurringApiRouter);
const api = express.Router();
api.use("/projects", projectsApiRouter);
api.use("/team-members", teamMembersApiRouter);
api.use("/job-titles", jobTitlesApiRouter);
api.use("/clients", clientsApiRouter);
api.use("/teams", teamsApiRouter);
api.use("/tasks", tasksApiRouter);
api.use("/settings", settingsApiRouter);
api.use("/personal-overview", personalOverviewApiRouter);
api.use("/statuses", statusesApiRouter);
api.use("/todo-list", todoListApiRouter);
api.use("/notifications", notificationsApiRouter);
api.use("/attachments", attachmentsApiRouter);
api.use("/sub-tasks", subTasksApiRouter);
api.use("/project-members", projectMembersApiRouter);
api.use("/task-time-log", taskWorkLogApiRouter);
api.use("/task-comments", taskCommentsApiRouter);
api.use("/timezones", timezonesApiRouter);
api.use("/project-statuses", projectStatusesApiRouter);
api.use("/labels", labelsApiRouter);
api.use("/resource-allocation", resourceAllocationApiRouter);
api.use("/shared/projects", sharedProjectsApiRouter);
api.use("/task-templates", taskTemplatesApiRouter);
api.use("/project-insights", projectInsightsApiRouter);
api.use("/admin-center", adminCenterApiRouter);
api.use("/reporting", reportingApiRouter);
api.use("/activity-logs", activityLogsApiRouter);
api.use("/projects-folders", projectFoldersApiRouter);
api.use("/task-phases", taskPhasesApiRouter);
api.use("/project-categories", projectCategoriesApiRouter);
api.use("/home", homePageApiRouter);
api.use("/gantt", ganttApiRouter);
api.use("/project-comments", projectCommentsApiRouter);
api.use("/reporting-export", reportingExportApiRouter);
api.use("/project-healths", projectHealthsApiRouter);
api.use("/project-templates", projectTemplatesApiRouter);
api.use("/pt-tasks", ptTasksApiRouter);
api.use("/pt-task-phases", ptTaskPhasesApiRouter);
api.use("/pt-statuses", ptStatusesApiRouter);
api.use("/workload-gannt", workloadApiRouter);
api.use("/roadmap-gannt", roadmapApiRouter);
api.use("/schedule-gannt", scheduleApiRouter);
api.use("/schedule-gannt-v2", scheduleApiV2Router);
api.use("/project-managers", projectManagerApiRouter);
api.get("/overview/:id", safeControllerFunction(OverviewController.getById));
api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get));
api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword));
api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles));
api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog));
api.use("/billing", billingApiRouter);
api.use("/task-dependencies", taskDependenciesApiRouter);
api.use("/task-recurring", taskRecurringApiRouter);
api.use("/custom-columns", customColumnsApiRouter);
api.use("/activity-logs", activityLogsApiRouter);
api.use("/project-activity-logs", projectActivityLogsApiRouter);
export default api;

View File

@@ -0,0 +1,22 @@
import express from "express";
import idParamValidator from "../../middlewares/validators/id-param-validator";
import safeControllerFunction from "../../shared/safe-controller-function";
import ProjectActivityLogsController from "../../controllers/project-activity-logs-controller";
const projectActivityLogsApiRouter = express.Router();
// Match the client URL (/project/:projectId) and ensure param name lines up
projectActivityLogsApiRouter.get(
"/project/:id",
// Validate that id is a proper UUID
idParamValidator,
// Wrap the controller to catch and forward errors
safeControllerFunction(ProjectActivityLogsController.getByProjectId)
);
// Optional healthcheck endpoint
projectActivityLogsApiRouter.get("/test", (req, res) => {
res.json({ message: "Project activity logs router is working!" });
});
export default projectActivityLogsApiRouter;

View File

@@ -0,0 +1,6 @@
node_modules
npm-debug.log
.git
.gitignore
.dockerignore
README.md

View File

@@ -12,7 +12,7 @@ COPY . .
RUN echo "window.VITE_API_URL='${VITE_API_URL:-http://backend:3000}';" > ./public/env-config.js && \
echo "window.VITE_SOCKET_URL='${VITE_SOCKET_URL:-ws://backend:3000}';" >> ./public/env-config.js
RUN npm run build
RUN NODE_OPTIONS="--max-old-space-size=4096" npm run build
FROM node:22-alpine AS production

View File

@@ -8,6 +8,7 @@ Worklenz is a project management application built with React, TypeScript, and A
- [Project Structure](#project-structure)
- [Contributing](#contributing)
- [Learn More](#learn-more)
- [License](#license)
## Getting Started
@@ -93,3 +94,7 @@ To learn more about the technologies used in this project:
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
- [Ant Design Documentation](https://ant.design/docs/react/introduce)
- [Vite Documentation](https://vitejs.dev/guide/)
## License
Worklenz is open source and released under the [GNU Affero General Public License Version 3 (AGPLv3)](LICENSE).

View File

@@ -14,6 +14,81 @@
<title>Worklenz</title>
<!-- Environment configuration -->
<script src="/env-config.js"></script>
<!-- Google Analytics -->
<script>
// Function to initialize Google Analytics
function initGoogleAnalytics() {
// Load the Google Analytics script
const script = document.createElement('script');
script.async = true;
// Determine which tracking ID to use based on the environment
const isProduction = window.location.hostname === 'worklenz.com' ||
window.location.hostname === 'app.worklenz.com';
const trackingId = isProduction
? 'G-XXXXXXXXXX'
: 'G-3LM2HGWEXG'; // Open source tracking ID
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
document.head.appendChild(script);
// Initialize Google Analytics
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', trackingId);
}
// Initialize analytics
initGoogleAnalytics();
// Function to show privacy notice
function showPrivacyNotice() {
const notice = document.createElement('div');
notice.style.cssText = `
position: fixed;
bottom: 16px;
right: 16px;
background: #222;
color: #f5f5f5;
padding: 12px 16px 10px 16px;
border-radius: 7px;
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
z-index: 1000;
max-width: 320px;
font-family: Inter, sans-serif;
border: 1px solid #333;
font-size: 0.95rem;
`;
notice.innerHTML = `
<div style="margin-bottom: 6px; font-weight: 600; color: #fff; font-size: 1rem;">Analytics Notice</div>
<div style="margin-bottom: 8px; color: #f5f5f5;">This app uses Google Analytics for anonymous usage stats. No personal data is tracked.</div>
<button id="analytics-notice-btn" style="padding: 5px 14px; background: #1890ff; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.95rem;">Got it</button>
`;
document.body.appendChild(notice);
// Add event listener to button
const btn = notice.querySelector('#analytics-notice-btn');
btn.addEventListener('click', function(e) {
e.preventDefault();
localStorage.setItem('privacyNoticeShown', 'true');
notice.remove();
});
}
// Wait for DOM to be ready
document.addEventListener('DOMContentLoaded', function() {
// Check if we should show the notice
const isProduction = window.location.hostname === 'worklenz.com' ||
window.location.hostname === 'app.worklenz.com';
const noticeShown = localStorage.getItem('privacyNoticeShown') === 'true';
// Show notice if not in production and not shown before
if (!isProduction && !noticeShown) {
showPrivacyNotice();
}
});
</script>
</head>
<body>

View File

@@ -63,6 +63,7 @@
"@types/node": "^20.8.4",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react-window-infinite-loader": "^1.0.9",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.2",
@@ -2449,6 +2450,27 @@
"@types/react": "*"
}
},
"node_modules/@types/react-window": {
"version": "1.8.8",
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz",
"integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-window-infinite-loader": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/react-window-infinite-loader/-/react-window-infinite-loader-1.0.9.tgz",
"integrity": "sha512-gEInTjQwURCnDOFyIEK2+fWB5gTjqwx30O62QfxA9stE5aiB6EWkGj4UMhc0axq7/FV++Gs/TGW8FtgEx0S6Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*",
"@types/react-window": "*"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",

View File

@@ -66,6 +66,7 @@
"@types/node": "^20.8.4",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react-window-infinite-loader": "^1.0.9",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.2",

View File

@@ -0,0 +1,4 @@
{
"doesNotExistText": "Na vjen keq, faqja që kërkoni nuk ekziston.",
"backHomeButton": "Kthehu në Faqen Kryesore"
}

View File

@@ -0,0 +1,31 @@
{
"continue": "Vazhdo",
"setupYourAccount": "Konfiguro Llogarinë Tënde në Worklenz.",
"organizationStepTitle": "Emërtoni Organizatën Tuaj",
"organizationStepLabel": "Zgjidhni një emër për llogarinë tuaj në Worklenz.",
"projectStepTitle": "Krijoni projektin tuaj të parë",
"projectStepLabel": "Në cilin projekt po punoni aktualisht?",
"projectStepPlaceholder": "p.sh. Plani i Marketingut",
"tasksStepTitle": "Krijoni detyrat tuaja të para",
"tasksStepLabel": "Shkruani disa detyra që do të kryeni në",
"tasksStepAddAnother": "Shto një tjetër",
"emailPlaceholder": "Adresa email",
"invalidEmail": "Ju lutemi vendosni një adresë email të vlefshme",
"or": "ose",
"templateButton": "Importo nga shablloni",
"goBack": "Kthehu Mbrapa",
"cancel": "Anulo",
"create": "Krijo",
"templateDrawerTitle": "Zgjidh nga shabllonet",
"step3InputLabel": "Fto me email",
"addAnother": "Shto një tjetër",
"skipForNow": "Kalo tani për tani",
"formTitle": "Krijoni detyrën tuaj të parë.",
"step3Title": "Fto ekipin tënd të punojë me",
"maxMembers": " (Mund të ftoni deri në 5 anëtarë)",
"maxTasks": " (Mund të krijoni deri në 5 detyra)"
}

View File

@@ -0,0 +1,113 @@
{
"title": "Faturimet",
"currentBill": "Fatura Aktuale",
"configuration": "Konfigurimi",
"currentPlanDetails": "Detajet e Planit Aktual",
"upgradePlan": "Përmirëso Planin",
"cardBodyText01": "Provë falas",
"cardBodyText02": "(Plani juaj i provës skadon në 1 muaj 19 ditë)",
"redeemCode": "Kodi i Zbritjes",
"accountStorage": "Depozita e Llogarisë",
"used": "Përdorur:",
"remaining": "E mbetur:",
"charges": "Tarifat",
"tooltip": "Tarifat për ciklin aktual të faturimit",
"description": "Përshkrimi",
"billingPeriod": "Periudha e Faturimit",
"billStatus": "Statusi i Faturës",
"perUserValue": "Vlera për Përdorues",
"users": "Përdoruesit",
"amount": "Shuma",
"invoices": "Faturat",
"transactionId": "ID e Transaksionit",
"transactionDate": "Data e Transaksionit",
"paymentMethod": "Metoda e Pagesës",
"status": "Statusi",
"ltdUsers": "Mund të shtoni deri në {{ltd_users}} përdorues.",
"totalSeats": "Vende totale",
"availableSeats": "Vende të disponueshme",
"addMoreSeats": "Shto më shumë vende",
"drawerTitle": "Kodi i Zbritjes",
"label": "Kodi i Zbritjes",
"drawerPlaceholder": "Vendosni kodin tuaj të zbritjes",
"redeemSubmit": "Paraqit",
"modalTitle": "Zgjidhni planin më të mirë për ekipin tuaj",
"seatLabel": "Numri i vendeve",
"freePlan": "Plan Falas",
"startup": "Startup",
"business": "Biznes",
"tag": "Më i Popullarizuar",
"enterprise": "Ndërmarrje",
"freeSubtitle": "falas përgjithmonë",
"freeUsers": "Më e mira për përdorim personal",
"freeText01": "100MB depozitë",
"freeText02": "3 projekte",
"freeText03": "5 anëtarë të ekipit",
"startupSubtitle": "ÇMIM I RASTËSISHËM / muaj",
"startupUsers": "Deri në 15 përdorues",
"startupText01": "25GB depozitë",
"startupText02": "Projekte të pakufizuara aktive",
"startupText03": "Orar",
"startupText04": "Raportim",
"startupText05": "Abonohu në projekte",
"businessSubtitle": "përdorues / muaj",
"businessUsers": "16 - 200 përdorues",
"enterpriseUsers": "200 - 500+ përdorues",
"footerTitle": "Ju lutemi na jepni një numër kontakti që mund të përdorim për t'ju kontaktuar.",
"footerLabel": "Numri i Kontaktit",
"footerButton": "Na kontaktoni",
"redeemCodePlaceHolder": "Vendosni kodin tuaj të zbritjes",
"submit": "Paraqit",
"trialPlan": "Provë Falas",
"trialExpireDate": "E vlefshme deri më {{trial_expire_date}}",
"trialExpired": "Provat tuaja falas skaduan {{trial_expire_string}}",
"trialInProgress": "Provat tuaja falas skadojnë {{trial_expire_string}}",
"required": "Kjo fushë është e detyrueshme",
"invalidCode": "Kod i pavlefshëm",
"selectPlan": "Zgjidhni planin më të mirë për ekipin tuaj",
"changeSubscriptionPlan": "Ndryshoni planin tuaj të abonimit",
"noOfSeats": "Numri i vendeve",
"annualPlan": "Pro - Vjetor",
"monthlyPlan": "Pro - Mujor",
"freeForever": "Falas Përgjithmonë",
"bestForPersonalUse": "Më e mira për përdorim personal",
"storage": "Depozitë",
"projects": "Projekte",
"teamMembers": "Anëtarët e Ekipit",
"unlimitedTeamMembers": "Anëtarë të pakufizuar të ekipit",
"unlimitedActiveProjects": "Projekte të pakufizuara aktive",
"schedule": "Orar",
"reporting": "Raportim",
"subscribeToProjects": "Abonohu në projekte",
"billedAnnually": "Faturuar çdo vit",
"billedMonthly": "Faturuar çdo muaj",
"pausePlan": "Pauzë Planin",
"resumePlan": "Rifillo Planin",
"changePlan": "Ndrysho Planin",
"cancelPlan": "Anulo Planin",
"perMonthPerUser": "për përdorues/muaj",
"viewInvoice": "Shiko Faturën",
"switchToFreePlan": "Kalo në Planin Falas",
"expirestoday": "sot",
"expirestomorrow": "nesër",
"expiredDaysAgo": "{{days}} ditë më parë",
"continueWith": "Vazhdo me {{plan}}",
"changeToPlan": "Ndrysho në {{plan}}"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Përmbledhje",
"name": "Emri i Organizatës",
"owner": "Pronari i Organizatës",
"admins": "Administruesit e Organizatës",
"contactNumber": "Shto Numrin e Kontaktit",
"edit": "Redakto"
}

View File

@@ -0,0 +1,12 @@
{
"membersCount": "Numri i Anëtarëve",
"createdAt": "Krijuar më",
"projectName": "Emri i Projektit",
"teamName": "Emri i Ekipit",
"refreshProjects": "Rifresko Projektet",
"searchPlaceholder": "Kërkoni sipas emrit të projektit",
"deleteProject": "Jeni i sigurt që dëshironi të fshini këtë projekt?",
"confirm": "Konfirmo",
"cancel": "Anulo",
"delete": "Fshi Projektin"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Përmbledhje",
"users": "Përdoruesit",
"teams": "Ekipet",
"billing": "Faturimi",
"projects": "Projektet",
"adminCenter": "Qendra Administrative"
}

View File

@@ -0,0 +1,33 @@
{
"title": "Ekipet",
"subtitle": "ekipet",
"tooltip": "Rifresko ekipet",
"placeholder": "Kërko sipas emrit",
"addTeam": "Shto Ekip",
"team": "Ekipi",
"membersCount": "Numri i Anëtarëve",
"members": "Anëtarët",
"drawerTitle": "Krijo Ekip të Ri",
"label": "Emri i Ekipit",
"drawerPlaceholder": "Emri",
"create": "Krijo",
"delete": "Fshi",
"settings": "Cilësimet",
"popTitle": "Jeni i sigurt?",
"message": "Ju lutemi shkruani një Emër",
"teamSettings": "Cilësimet e Ekipit",
"teamName": "Emri i Ekipit",
"teamDescription": "Përshkrimi i Ekipit",
"teamMembers": "Anëtarët e Ekipit",
"teamMembersCount": "Numri i Anëtarëve të Ekipit",
"teamMembersPlaceholder": "Kërko sipas emrit",
"addMember": "Shto Anëtar",
"add": "Shto",
"update": "Përditëso",
"teamNamePlaceholder": "Emri i ekipit",
"user": "Përdoruesi",
"role": "Roli",
"owner": "Pronari",
"admin": "Administruesi",
"member": "Anëtari"
}

View File

@@ -0,0 +1,9 @@
{
"title": "Përdoruesit",
"subTitle": "përdoruesit",
"placeholder": "Kërko sipas emrit",
"user": "Përdoruesi",
"email": "Email",
"lastActivity": "Aktiviteti i Fundit",
"refresh": "Rifresko përdoruesit"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Emri",
"client": "Klienti",
"category": "Kategoria",
"status": "Statusi",
"tasksProgress": "Progresi i Detyrave",
"updated_at": "Përditësuar Së Fundi",
"members": "Anëtarët",
"setting": "Cilësimet",
"projects": "Projektet",
"refreshProjects": "Rifresko projektet",
"all": "Të Gjitha",
"favorites": "Të Preferuarat",
"archived": "Të Arkivuara",
"placeholder": "Kërko sipas emrit",
"archive": "Arkivo",
"unarchive": "Ç'arkivo",
"archiveConfirm": "Jeni i sigurt që doni ta arkivoni këtë projekt?",
"unarchiveConfirm": "Jeni i sigurt që doni ta çarkivoni këtë projekt?",
"clickToFilter": "Klikoni për të filtruar sipas",
"noProjects": "Nuk u gjetën projekte",
"addToFavourites": "Shto në të preferuarat"
}

View File

@@ -0,0 +1,5 @@
{
"loggingOut": "Po dilni...",
"authenticating": "Po autentikoheni...",
"gettingThingsReady": "Po përgatiten gjërat për ju..."
}

View File

@@ -0,0 +1,12 @@
{
"headerDescription": "Rivendosni fjalëkalimin tuaj",
"emailLabel": "Email",
"emailPlaceholder": "Vendosni email-in tuaj",
"emailRequired": "Ju lutemi vendosni Email-in tuaj!",
"resetPasswordButton": "Rivendos Fjalëkalimin",
"returnToLoginButton": "Kthehu te Hyrja",
"passwordResetSuccessMessage": "Një lidhje për rivendosjen e fjalëkalimit është dërguar në email-in tuaj.",
"orText": "OSE",
"successTitle": "U dërguan udhëzimet për rivendosje!",
"successMessage": "Informacioni për rivendosje është dërguar në email-in tuaj. Ju lutemi kontrolloni email-in."
}

View File

@@ -0,0 +1,27 @@
{
"headerDescription": "Hyni në llogarinë tuaj",
"emailLabel": "Email",
"emailPlaceholder": "Vendosni email-in tuaj",
"emailRequired": "Ju lutemi vendosni Email-in tuaj!",
"passwordLabel": "Fjalëkalimi",
"passwordPlaceholder": "Vendosni fjalëkalimin",
"passwordRequired": "Ju lutemi vendosni Fjalëkalimin!",
"rememberMe": "Më mbaj mend",
"loginButton": "Hyr",
"signupButton": "Regjistrohu",
"forgotPasswordButton": "Keni harruar fjalëkalimin?",
"signInWithGoogleButton": "Hyr me Google",
"dontHaveAccountText": "Nuk keni llogari?",
"orText": "OSE",
"successMessage": "Jeni futur me sukses!",
"loginError": "Hyrja dështoi",
"googleLoginError": "Hyrja përmes Google dështoi",
"validationMessages": {
"email": "Ju lutemi vendosni një adresë email të vlefshme",
"password": "Fjalëkalimi duhet të jetë së paku 8 karaktere"
},
"errorMessages": {
"loginErrorTitle": "Hyrja dështoi",
"loginErrorMessage": "Ju lutemi kontrolloni email-in dhe fjalëkalimin dhe provoni përsëri"
}
}

View File

@@ -0,0 +1,29 @@
{
"headerDescription": "Regjistrohuni për të filluar",
"nameLabel": "Emri i Plotë",
"namePlaceholder": "Shkruani emrin tuaj të plotë",
"nameRequired": "Ju lutemi shkruani emrin tuaj të plotë!",
"nameMinCharacterRequired": "Emri duhet të jetë së paku 4 karaktere!",
"emailLabel": "Email",
"emailPlaceholder": "Shkruani email-in tuaj",
"emailRequired": "Ju lutemi shkruani Email-in tuaj!",
"passwordLabel": "Fjalëkalimi",
"passwordPlaceholder": "Krijoni një fjalëkalim",
"passwordRequired": "Ju lutemi krijoni një Fjalëkalim!",
"passwordMinCharacterRequired": "Fjalëkalimi duhet të jetë së paku 8 karaktere!",
"passwordPatternRequired": "Fjalëkalimi nuk plotëson kërkesat!",
"strongPasswordPlaceholder": "Vendosni një fjalëkalim më të fortë",
"passwordValidationAltText": "Fjalëkalimi duhet të përmbajë së paku 8 karaktere me shkronja të mëdha dhe të vogla, një numër dhe një simbol.",
"signupSuccessMessage": "Jeni regjistruar me sukses!",
"privacyPolicyLink": "Politika e Privatësisë",
"termsOfUseLink": "Kushtet e Përdorimit",
"bySigningUpText": "Duke u regjistruar, ju pranoni",
"andText": "dhe",
"signupButton": "Regjistrohu",
"signInWithGoogleButton": "Hyr me Google",
"alreadyHaveAccountText": "Keni tashmë një llogari?",
"loginButton": "Hyr",
"orText": "OSE",
"reCAPTCHAVerificationError": "Gabim në Verifikimin e reCAPTCHA",
"reCAPTCHAVerificationErrorMessage": "Nuk mundëm të verifikojmë reCAPTCHA-n tuaj. Ju lutemi provoni përsëri."
}

View File

@@ -0,0 +1,14 @@
{
"title": "Verifikoni Email-in për Rivendosje",
"description": "Vendosni fjalëkalimin tuaj të ri",
"placeholder": "Vendosni fjalëkalimin tuaj të ri",
"confirmPasswordPlaceholder": "Konfirmoni fjalëkalimin e ri",
"passwordHint": "Të paktën 8 karaktere, me shkronja të mëdha dhe të vogla, një numër dhe një simbol.",
"resetPasswordButton": "Rivendos fjalëkalimin",
"orText": "Ose",
"resendResetEmail": "Dërgo përsëri email-in e rivendosjes",
"passwordRequired": "Ju lutemi vendosni fjalëkalimin e ri",
"returnToLoginButton": "Kthehu te Hyrja",
"confirmPasswordRequired": "Ju lutemi konfirmoni fjalëkalimin e ri",
"passwordMismatch": "Fjalëkalimet nuk përputhen"
}

View File

@@ -0,0 +1,9 @@
{
"login-success": "Hyrja u krye me sukses!",
"login-failed": "Hyrja dështoi. Ju lutemi kontrolloni kredencialet dhe provoni përsëri.",
"signup-success": "Regjistrimi u krye me sukses! Mirë se erdhët.",
"signup-failed": "Regjistrimi dështoi. Ju lutemi sigurohuni që të gjitha fushat e nevojshme janë plotësuar dhe provoni përsëri.",
"reconnecting": "Jeni shkëputur nga serveri.",
"connection-lost": "Lidhja me serverin dështoi. Ju lutemi kontrolloni lidhjen tuaj me internet.",
"connection-restored": "U lidhët me serverin me sukses"
}

View File

@@ -0,0 +1,13 @@
{
"formTitle": "Krijoni projektin tuaj të parë",
"inputLabel": "Në cilin projekt po punoni aktualisht?",
"or": "ose",
"templateButton": "Importo nga shablloni",
"createFromTemplate": "Krijo nga shablloni",
"goBack": "Kthehu Mbrapa",
"continue": "Vazhdo",
"cancel": "Anulo",
"create": "Krijo",
"templateDrawerTitle": "Zgjidh nga shabllonet",
"createProject": "Krijo Projekt"
}

View File

@@ -0,0 +1,7 @@
{
"formTitle": "Krijo detyrën tënde të parë.",
"inputLabel": "Shkruaj disa detyra që do të kryesh në",
"addAnother": "Shto një tjetër",
"goBack": "Kthehu mbrapa",
"continue": "Vazhdo"
}

View File

@@ -0,0 +1,46 @@
{
"todoList": {
"title": "Lista e Detyrave",
"refreshTasks": "Rifresko detyrat",
"addTask": "+ Shto Detyrë",
"noTasks": "Asnjë detyrë",
"pressEnter": "Shtyp",
"toCreate": "për të krijuar.",
"markAsDone": "Shëno si të përfunduar"
},
"projects": {
"title": "Projektet",
"refreshProjects": "Rifresko projektet",
"noRecentProjects": "Aktualisht nuk jeni caktuar në asnjë projekt.",
"noFavouriteProjects": "Asnjë projekt i shënuar si i preferuar.",
"recent": "Të Fundit",
"favourites": "Të Preferuarat"
},
"tasks": {
"assignedToMe": "Më janë caktuar",
"assignedByMe": "I kam caktuar",
"all": "Të Gjitha",
"today": "Sot",
"upcoming": "Ardhj",
"overdue": "Të vonuara",
"noDueDate": "Pa afat",
"noTasks": "Asnjë detyrë për të shfaqur.",
"addTask": "+ Shto detyrë",
"name": "Emri",
"project": "Projekti",
"status": "Statusi",
"dueDate": "Afati",
"dueDatePlaceholder": "Cakto Afatin",
"tomorrow": "Nesër",
"nextWeek": "Javën e Ardhshme",
"nextMonth": "Muajin e Ardhshëm",
"projectRequired": "Ju lutemi zgjidhni një projekt",
"pressTabToSelectDueDateAndProject": "Shtyp Tab për të zgjedhur afatin dhe projektin",
"dueOn": "Detyrat me afat më",
"taskRequired": "Ju lutemi shtoni një detyrë",
"list": "Listë",
"calendar": "Kalendar",
"tasks": "Detyrat",
"refresh": "Rifresko"
}
}

View File

@@ -0,0 +1,8 @@
{
"formTitle": "Fto ekipin tënd të punojë me",
"inputLabel": "Fto me email",
"addAnother": "Shto një tjetër",
"goBack": "Kthehu mbrapa",
"continue": "Vazhdo",
"skipForNow": "Anashkalo tani për tani"
}

View File

@@ -0,0 +1,23 @@
{
"rename": "Riemërto",
"delete": "Fshi",
"addTask": "Shto Detyrë",
"addSectionButton": "Shto Seksion",
"changeCategory": "Ndrysho kategorinë",
"deleteTooltip": "Fshi",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"dueDate": "Data e përfundimit",
"cancel": "Anulo",
"today": "Sot",
"tomorrow": "Nesër",
"assignToMe": "Cakto mua",
"archive": "Arkivo",
"newTaskNamePlaceholder": "Shkruaj emrin e detyrës",
"newSubtaskNamePlaceholder": "Shkruaj emrin e nëndetyrës"
}

View File

@@ -0,0 +1,6 @@
{
"title": "Prova juaj e Worklenz ka skaduar!",
"subtitle": "Ju lutemi përmirësoni tani.",
"button": "Përmirëso tani",
"checking": "Po kontrollohet statusi i abonimit..."
}

View File

@@ -0,0 +1,31 @@
{
"logoAlt": "Logoja e Worklenz",
"home": "Kryefaqja",
"projects": "Projektet",
"schedule": "Orari",
"reporting": "Raportimi",
"clients": "Klientët",
"teams": "Ekipet",
"labels": "Etiketa",
"jobTitles": "Tituj Pune",
"upgradePlan": "Përmirëso Abonimin",
"upgradePlanTooltip": "Përmirëso abonimin",
"invite": "Fto",
"inviteTooltip": "Fto anëtarë të ekipit të bashkohen",
"switchTeamTooltip": "Ndrysho ekipin",
"help": "Ndihmë",
"notificationTooltip": "Shiko njoftimet",
"profileTooltip": "Shiko profilin",
"adminCenter": "Qendra Administrative",
"settings": "Cilësimet",
"logOut": "Dil",
"notificationsDrawer": {
"read": "Lexuara e njoftimet ",
"unread": "Njoftimet e palexuara",
"markAsRead": "Shëno si të lexuara",
"readAndJoin": "Lexo & Bashkohu",
"accept": "Prano",
"acceptAndJoin": "Prano & Bashkohu",
"noNotifications": "Asnjë njoftim"
}
}

View File

@@ -0,0 +1,5 @@
{
"nameYourOrganization": "Emërtoni organizatën tuaj.",
"worklenzAccountTitle": "Zgjidhni një emër për llogarinë tuaj në Worklenz.",
"continue": "Vazhdo"
}

View File

@@ -0,0 +1,7 @@
{
"configurePhases": "Konfiguro Fazat",
"phaseLabel": "Etiketa e Fazës",
"enterPhaseName": "Vendosni një emër për etiketën e fazës",
"addOption": "Shto Opsion",
"phaseOptions": "Opsionet e Fazës:"
}

View File

@@ -0,0 +1,42 @@
{
"createProject": "Krijo Projekt",
"editProject": "Modifiko Projektin",
"enterCategoryName": "Vendosni emër për kategorinë",
"hitEnterToCreate": "Shtyp Enter për të krijuar!",
"enterNotes": "Shënime",
"youCanManageClientsUnderSettings": "Mund të menaxhoni klientët nën Cilësimet",
"addCategory": "Shto kategori projektit",
"newCategory": "Kategori e Re",
"notes": "Shënime",
"startDate": "Data e Fillimit",
"endDate": "Data e Përfundimit",
"estimateWorkingDays": "Vlerëso ditët e punës",
"estimateManDays": "Vlerëso ditët e punëtorëve",
"hoursPerDay": "Orë në ditë",
"create": "Krijo",
"update": "Përditëso",
"delete": "Fshi",
"typeToSearchClients": "Shkruani për të kërkuar klientë",
"projectColor": "Ngjyra e Projektit",
"pleaseEnterAName": "Ju lutemi vendosni një emër",
"enterProjectName": "Vendosni emrin e projektit",
"name": "Emri",
"status": "Statusi",
"health": "Gjendja",
"category": "Kategoria",
"projectManager": "Menaxheri i Projektit",
"client": "Klienti",
"deleteConfirmation": "Jeni i sigurt që doni të fshini?",
"deleteConfirmationDescription": "Kjo do të fshijë të gjitha të dhënat e lidhura dhe nuk mund të zhbëhet.",
"yes": "Po",
"no": "Jo",
"createdAt": "Krijuar më",
"updatedAt": "Përditësuar më",
"by": "nga",
"add": "Shto",
"asClient": "si klient",
"createClient": "Krijo klient",
"searchInputPlaceholder": "Kërko sipas emrit ose emailit",
"hoursPerDayValidationMessage": "Orët në ditë duhet të jenë një numër midis 1 dhe 24",
"noPermission": "Nuk ka leje"
}

View File

@@ -0,0 +1,14 @@
{
"nameColumn": "Emri",
"attachedTaskColumn": "Detyra e Bashkangjitur",
"sizeColumn": "Madhësia",
"uploadedByColumn": "Ngarkuar Nga",
"uploadedAtColumn": "Ngarkuar Më",
"fileIconAlt": "Ikona e skedarit",
"titleDescriptionText": "Të gjitha bashkëngjitjet e detyrave në këtë projekt do të shfahen këtu.",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"segmentedTooltip": "Së shpejti! Kaloni midis pamjes listë dhe pamjes miniaturash.",
"emptyText": "Nuk ka bashkëngjitje në projekt."
}

View File

@@ -0,0 +1,41 @@
{
"overview": {
"title": "Përmbledhje",
"statusOverview": "Përmbledhje Statusi",
"priorityOverview": "Përmbledhje Prioriteti",
"lastUpdatedTasks": "Detyrat e Përditësuara Së Fundi"
},
"members": {
"title": "Anëtarët",
"tooltip": "Anëtarët",
"tasksByMembers": "Detyrat sipas anëtarëve",
"tasksByMembersTooltip": "Detyrat sipas anëtarëve",
"name": "Emri",
"taskCount": "Numri i Detyrave",
"contribution": "Kontributi",
"completed": "Të Përfunduara",
"incomplete": "Të Papërfunduara",
"overdue": "Të Vonuara",
"progress": "Progresi"
},
"tasks": {
"overdueTasks": "Detyrat e Vonuara",
"overLoggedTasks": "Detyrat me regjistrim të tepërt",
"tasksCompletedEarly": "Detyrat e përfunduara para afatit",
"tasksCompletedLate": "Detyrat e përfunduara pas afatit",
"overLoggedTasksTooltip": "Detyrat me kohë të regjistruar mbi kohën e vlerësuar",
"overdueTasksTooltip": "Detyrat që kanë kaluar afatin e tyre"
},
"common": {
"seeAll": "Shiko të gjitha",
"totalLoggedHours": "Orët totale të regjistruara",
"totalEstimation": "Vlerësimi total",
"completedTasks": "Detyrat e përfunduara",
"incompleteTasks": "Detyrat e papërfunduara",
"overdueTasks": "Detyrat e vonuara",
"overdueTasksTooltip": "Detyrat që kanë kaluar afatin e tyre",
"totalLoggedHoursTooltip": "Vlerësimi dhe koha e regjistruar për detyrat.",
"includeArchivedTasks": "Përfshi Detyrat e Arkivuara",
"export": "Eksporto"
}
}

View File

@@ -0,0 +1,17 @@
{
"nameColumn": "Emri",
"jobTitleColumn": "Titulli i Punës",
"emailColumn": "Email",
"tasksColumn": "Detyrat",
"taskProgressColumn": "Progresi i Detyrave",
"accessColumn": "Qasja",
"fileIconAlt": "Ikona e skedarit",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"refreshButtonTooltip": "Rifresko anëtarët",
"deleteButtonTooltip": "Hiq nga projekti",
"memberCount": "Anëtar",
"membersCountPlural": "Anëtarë",
"emptyText": "Nuk ka bashkëngjitje në projekt."
}

View File

@@ -0,0 +1,6 @@
{
"inputPlaceholder": "Shto një koment..",
"addButton": "Shto",
"cancelButton": "Anulo",
"deleteButton": "Fshi"
}

View File

@@ -0,0 +1,11 @@
{
"importTaskTemplate": "Importo Shabllon Detyrash",
"templateName": "Emri i Shabllonit",
"templateDescription": "Përshkrimi i Shabllonit",
"selectedTasks": "Detyrat e Përzgjedhura",
"tasks": "Detyrat",
"templates": "Shabllonet",
"remove": "Hiq",
"cancel": "Anulo",
"import": "Importo"
}

View File

@@ -0,0 +1,7 @@
{
"title": "Anëtarët e Projektit",
"searchLabel": "Shtoni anëtarë duke shkruar emrin ose email-in e tyre",
"searchPlaceholder": "Shkruani emrin ose email-in",
"inviteAsAMember": "Fto si anëtar",
"inviteNewMemberByEmail": "Fto anëtar të ri me email"
}

View File

@@ -0,0 +1,13 @@
{
"importTasks": "Importo detyra",
"createTask": "Krijo detyrë",
"settings": "Cilësimet",
"subscribe": "Abonohu",
"unsubscribe": "Ç'abonohu",
"deleteProject": "Fshi projektin",
"startDate": "Data e fillimit",
"endDate": "Data e përfundimit",
"projectSettings": "Cilësimet e projektit",
"projectSummary": "Përmbledhja e projektit",
"receiveProjectSummary": "Merrni një përmbledhje të projektit çdo mbrëmje."
}

View File

@@ -0,0 +1,27 @@
{
"title": "Ruaj si Shabllon",
"templateName": "Emri i Shabllonit",
"includes": "Çfarë duhet të përfshihet në shabllon nga projekti?",
"includesOptions": {
"statuses": "Statuset",
"phases": "Fazat",
"labels": "Etiketat"
},
"taskIncludes": "Çfarë duhet të përfshihet në shabllon nga detyrat?",
"taskIncludesOptions": {
"statuses": "Statuset",
"phases": "Fazat",
"labels": "Etiketat",
"name": "Emri",
"priority": "Prioriteti",
"status": "Statusi",
"phase": "Faza",
"label": "Etiketa",
"timeEstimate": "Vlerësimi i Kohës",
"description": "Përshkrimi",
"subTasks": "Nëndetyrat"
},
"cancel": "Anulo",
"save": "Ruaj",
"templateNamePlaceholder": "Shkruani emrin e shabllonit"
}

View File

@@ -0,0 +1,90 @@
{
"exportButton": "Eksporto",
"timeLogsButton": "Regjistrimet e Kohës",
"activityLogsButton": "Regjistrimet e Aktivitetit",
"tasksButton": "Detyrat",
"searchByNameInputPlaceholder": "Kërko sipas emrit",
"overviewTab": "Përmbledhje",
"timeLogsTab": "Regjistrimet e Kohës",
"activityLogsTab": "Regjistrimet e Aktivitetit",
"tasksTab": "Detyrat",
"projectsText": "Projektet",
"totalTasksText": "Detyrat Gjithsej",
"assignedTasksText": "Detyrat e Caktuara",
"completedTasksText": "Detyrat e Përfunduara",
"ongoingTasksText": "Detyrat në Vazhdim",
"overdueTasksText": "Detyrat e Vonuara",
"loggedHoursText": "Orët e Regjistruara",
"tasksText": "Detyrat",
"allText": "Të Gjitha",
"tasksByProjectsText": "Detyrat Sipas Projekteve",
"tasksByStatusText": "Detyrat Sipas Statusit",
"tasksByPriorityText": "Detyrat Sipas Prioritetit",
"todoText": "Për Të Bërë",
"doingText": "Duke bërë",
"doneText": "E Përfunduar",
"lowText": "I Ulët",
"mediumText": "I Mesëm",
"highText": "I Lartë",
"billableButton": "Fakturueshme",
"billableText": "Fakturueshme",
"nonBillableText": "Jo Fakturueshme",
"timeLogsEmptyPlaceholder": "Asnjë regjistrim kohe për të shfaqur",
"loggedText": "Regjistruar",
"forText": "për",
"inText": "në",
"updatedText": "Përditësuar",
"fromText": "Nga",
"toText": "në",
"withinText": "brenda",
"activityLogsEmptyPlaceholder": "Asnjë regjistrim aktiviteti për të shfaqur",
"filterByText": "Filtro sipas:",
"selectProjectPlaceholder": "Zgjidh Projektin",
"taskColumn": "Detyra",
"nameColumn": "Emri",
"projectColumn": "Projekti",
"statusColumn": "Statusi",
"priorityColumn": "Prioriteti",
"dueDateColumn": "Afati",
"completedDateColumn": "Data e Përfundimit",
"estimatedTimeColumn": "Koha e Vlerësuar",
"loggedTimeColumn": "Koha e Regjistruar",
"overloggedTimeColumn": "Koha e Tepërt",
"daysLeftColumn": "Ditë të Mbetura/Vonuar",
"startDateColumn": "Data e Fillimit",
"endDateColumn": "Data e Përfundimit",
"actualTimeColumn": "Koha Aktuale",
"projectHealthColumn": "Gjendja e Projektit",
"categoryColumn": "Kategoria",
"projectManagerColumn": "Menaxheri i Projektit",
"tasksStatsOverviewDrawerTitle": "Detyrat e ",
"projectsStatsOverviewDrawerTitle": "Projektet e ",
"cancelledText": "Anuluar",
"blockedText": "E Bllokuar",
"onHoldText": "Në Pritje",
"proposedText": "E Propozuar",
"inPlanningText": "Në Planifikim",
"inProgressText": "Në Progres",
"completedText": "E Përfunduar",
"continuousText": "E Vazhdueshme",
"daysLeftText": "ditë të mbetura",
"daysOverdueText": "ditë vonuar",
"notSetText": "Pa Caktuar",
"needsAttentionText": "Kërkon Vëmendje",
"atRiskText": "Në Rrezik",
"goodText": "Në Rregull"
}

View File

@@ -0,0 +1,35 @@
{
"yesterdayText": "Dje",
"lastSevenDaysText": "7 Ditët e Fundit",
"lastWeekText": "Javën e Kaluar",
"lastThirtyDaysText": "30 Ditët e Fundit",
"lastMonthText": "Muajin e Kaluar",
"lastThreeMonthsText": "3 Muajt e Fundit",
"allTimeText": "Të Gjitha",
"customRangeText": "Interval i Përshtatur",
"startDateInputPlaceholder": "Data e fillimit",
"EndDateInputPlaceholder": "Data e përfundimit",
"filterButton": "Filtro",
"membersTitle": "Anëtarët",
"includeArchivedButton": "Përfshij Projektet e Arkivuara",
"exportButton": "Eksporto",
"excelButton": "Excel",
"searchByNameInputPlaceholder": "Kërko sipas emrit",
"memberColumn": "Anëtari",
"tasksProgressColumn": "Progresi i Detyrave",
"tasksAssignedColumn": "Detyrat e Caktuara",
"completedTasksColumn": "Detyrat e Përfunduara",
"overdueTasksColumn": "Detyrat e Vonuara",
"ongoingTasksColumn": "Detyrat në Vazhdim",
"tasksAssignedColumnTooltip": "Detyrat e caktuara në intervalin e zgjedhur",
"overdueTasksColumnTooltip": "Detyrat e vonuara deri në fund të intervalit të zgjedhur",
"completedTasksColumnTooltip": "Detyrat e përfunduara në intervalin e zgjedhur",
"ongoingTasksColumnTooltip": "Detyrat e filluara por jo të përfunduara ende",
"todoText": "Për Të Bërë",
"doingText": "Duke bërë",
"doneText": "E Përfunduar"
}

View File

@@ -0,0 +1,39 @@
{
"exportButton": "Eksporto",
"projectsButton": "Projektet",
"membersButton": "Anëtarët",
"searchByNameInputPlaceholder": "Kërko sipas emrit",
"overviewTab": "Përmbledhje",
"projectsTab": "Projektet",
"membersTab": "Anëtarët",
"projectsByStatusText": "Projektet Sipas Statusit",
"projectsByCategoryText": "Projektet Sipas Kategorisë",
"projectsByHealthText": "Projektet Sipas Gjendjes",
"projectsText": "Projektet",
"allText": "Të Gjitha",
"cancelledText": "Anuluar",
"blockedText": "E Bllokuar",
"onHoldText": "Në Pritje",
"proposedText": "E Propozuar",
"inPlanningText": "Në Planifikim",
"inProgressText": "Në Progres",
"completedText": "E Përfunduar",
"continuousText": "E Vazhdueshme",
"notSetText": "Pa Caktuar",
"needsAttentionText": "Kërkon Vëmendje",
"atRiskText": "Në Rrezik",
"goodText": "Në Rregull",
"nameColumn": "Emri",
"emailColumn": "Email",
"projectsColumn": "Projektet",
"tasksColumn": "Detyrat",
"overdueTasksColumn": "Detyrat e Vonuara",
"completedTasksColumn": "Detyrat e Përfunduara",
"ongoingTasksColumn": "Detyrat në Vazhdim"
}

View File

@@ -0,0 +1,25 @@
{
"overviewTitle": "Përmbledhje",
"includeArchivedButton": "Përfshij Projektet e Arkivuara",
"teamCount": "Ekip",
"teamCountPlural": "Ekipe",
"projectCount": "Projekt",
"projectCountPlural": "Projekte",
"memberCount": "Anëtar",
"memberCountPlural": "Anëtarë",
"activeProjectCount": "Projekt Aktiv",
"activeProjectCountPlural": "Projekte Aktive",
"overdueProjectCount": "Projekt i Vonuar",
"overdueProjectCountPlural": "Projekte të Vonuara",
"unassignedMemberCount": "Anëtar i Pacaktuar",
"unassignedMemberCountPlural": "Anëtarë të Pacaktuar",
"memberWithOverdueTaskCount": "Anëtar me Detyrë të Vonuar",
"memberWithOverdueTaskCountPlural": "Anëtarë me Detyra të Vonuara",
"teamsText": "Ekipet",
"nameColumn": "Emri",
"projectsColumn": "Projektet",
"membersColumn": "Anëtarët"
}

View File

@@ -0,0 +1,59 @@
{
"exportButton": "Eksporto",
"membersButton": "Anëtarët",
"tasksButton": "Detyrat",
"searchByNameInputPlaceholder": "Kërko sipas emrit",
"overviewTab": "Përmbledhje",
"membersTab": "Anëtarët",
"tasksTab": "Detyrat",
"completedTasksText": "Detyrat e Përfunduara",
"incompleteTasksText": "Detyrat e Papërfunduara",
"overdueTasksText": "Detyrat e Vonuara",
"allocatedHoursText": "Orët e Alokuara",
"loggedHoursText": "Orët e Regjistruara",
"tasksText": "Detyrat",
"allText": "Të Gjitha",
"tasksByStatusText": "Detyrat Sipas Statusit",
"tasksByPriorityText": "Detyrat Sipas Prioritetit",
"tasksByDueDateText": "Detyrat Sipas Afatit",
"todoText": "Për Të Bërë",
"doingText": "Duke bërë",
"doneText": "E Përfunduar",
"lowText": "I Ulët",
"mediumText": "I Mesëm",
"highText": "I Lartë",
"completedText": "E Përfunduar",
"upcomingText": "Në Ardhje",
"overdueText": "E Vonuar",
"noDueDateText": "Pa Afat",
"nameColumn": "Emri",
"tasksCountColumn": "Numri i Detyrave",
"completedTasksColumn": "Detyrat e Përfunduara",
"incompleteTasksColumn": "Detyrat e Papërfunduara",
"overdueTasksColumn": "Detyrat e Vonuara",
"contributionColumn": "Kontributi",
"progressColumn": "Progresi",
"loggedTimeColumn": "Koha e Regjistruar",
"taskColumn": "Detyra",
"projectColumn": "Projekti",
"statusColumn": "Statusi",
"priorityColumn": "Prioriteti",
"phaseColumn": "Faza",
"dueDateColumn": "Afati",
"completedDateColumn": "Data e Përfundimit",
"estimatedTimeColumn": "Koha e Vlerësuar",
"overloggedTimeColumn": "Koha e Tepërt",
"completedOnColumn": "Përfunduar Më",
"daysOverdueColumn": "Ditë vonim",
"groupByText": "Grupo Sipas:",
"statusText": "Statusi",
"priorityText": "Prioriteti",
"phaseText": "Faza"
}

View File

@@ -0,0 +1,35 @@
{
"searchByNamePlaceholder": "Kërko sipas emrit",
"searchByCategoryPlaceholder": "Kërko sipas kategorisë",
"statusText": "Statusi",
"healthText": "Gjendja",
"categoryText": "Kategoria",
"projectManagerText": "Menaxheri i Projektit",
"showFieldsText": "Shfaq fushat",
"cancelledText": "Anuluar",
"blockedText": "E bllokuar",
"onHoldText": "Në pritje",
"proposedText": "E propozuar",
"inPlanningText": "Në planifikim",
"inProgressText": "Në progres",
"completedText": "E përfunduar",
"continuousText": "E vazhdueshme",
"notSetText": "Pa caktuar",
"needsAttentionText": "Kërkon vëmendje",
"atRiskText": "Në rrezik",
"goodText": "Në rregull",
"nameText": "Projekti",
"estimatedVsActualText": "Vlerësuar vs Aktual",
"tasksProgressText": "Progresi i detyrave",
"lastActivityText": "Aktiviteti i fundit",
"datesText": "Datat e Fillimit/Përfundimit",
"daysLeftText": "Ditë të mbetura/vonuar",
"projectHealthText": "Gjendja e projektit",
"projectUpdateText": "Përditësimi i projektit",
"clientText": "Klienti",
"teamText": "Ekipi"
}

View File

@@ -0,0 +1,52 @@
{
"projectCount": "Projekt",
"projectCountPlural": "Projekte",
"includeArchivedButton": "Përfshij Projektet e Arkivuara",
"exportButton": "Eksporto",
"excelButton": "Excel",
"projectColumn": "Projekti",
"estimatedVsActualColumn": "Vlerësuar vs Aktual",
"tasksProgressColumn": "Progresi i Detyrave",
"lastActivityColumn": "Aktiviteti i Fundit",
"statusColumn": "Statusi",
"datesColumn": "Data e Fillimit/Përfundimit",
"daysLeftColumn": "Ditë të Mbetura/Vonuar",
"projectHealthColumn": "Gjendja e Projektit",
"categoryColumn": "Kategoria",
"projectUpdateColumn": "Përditësimi i Projektit",
"clientColumn": "Klienti",
"teamColumn": "Ekipi",
"projectManagerColumn": "Menaxheri i Projektit",
"openButton": "Hap",
"estimatedText": "Vlerësuar",
"actualText": "Aktual",
"todoText": "Për të Bërë",
"doingText": "duke bërë",
"doneText": "E Përfunduar",
"cancelledText": "Anuluar",
"blockedText": "E Bllokuar",
"onHoldText": "Në Pritje",
"proposedText": "E Propozuar",
"inPlanningText": "Në Planifikim",
"inProgressText": "Në Progres",
"completedText": "E Përfunduar",
"continuousText": "E Vazhdueshme",
"daysLeftText": "ditë të mbetura",
"dayLeftText": "ditë e mbetur",
"daysOverdueText": "ditë vonuar",
"notSetText": "Pa Caktuar",
"needsAttentionText": "Kërkon Vëmendje",
"atRiskText": "Në Rrezik",
"goodText": "Në Rregull",
"setCategoryText": "Cakto Kategorinë",
"searchByNameInputPlaceholder": "Kërko sipas emrit",
"todayText": "Sot"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Përmbledhje",
"projects": "Projektet",
"members": "Anëtarët",
"timeReports": "Raportet e Kohës",
"estimateVsActual": "Vlerësimi vs Aktual",
"currentOrganizationTooltip": "Organizata aktuale"
}

View File

@@ -0,0 +1,39 @@
{
"today": "Sot",
"week": "Javë",
"month": "Muaj",
"settings": "Cilësimet",
"workingDays": "Ditët e punës",
"monday": "E hënë",
"tuesday": "E martë",
"wednesday": "E mërkurë",
"thursday": "E enjte",
"friday": "E premte",
"saturday": "E shtunë",
"sunday": "E diel",
"workingHours": "Orët e punës",
"hours": "Orë",
"saveButton": "Ruaj",
"totalAllocation": "Alokimi Total",
"timeLogged": "Koha e Regjistruar",
"remainingTime": "Koha e Mbetur",
"total": "Total",
"perDay": "Në Ditë",
"tasks": "detyra",
"startDate": "Data e Fillimit",
"endDate": "Data e Përfundimit",
"hoursPerDay": "Orë Në Ditë",
"totalHours": "Orë Totale",
"deleteButton": "Fshi",
"cancelButton": "Anulo",
"tabTitle": "Detyra pa Data Fillimi & Përfundimi",
"allocatedTime": "Koha e alokuar",
"totalLogged": "Total i Regjistruar",
"loggedBillable": "Regjistruar Fakturueshme",
"loggedNonBillable": "Regjistruar Jo Fakturueshme"
}

View File

@@ -0,0 +1,10 @@
{
"categoryColumn": "Kategoria",
"deleteConfirmationTitle": "Jeni të sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"associatedTaskColumn": "Projektet e Lidhura",
"searchPlaceholder": "Kërko sipas emrit",
"emptyText": "Kategoritë mund të krijohen gjatë përditësimit ose krijimit të projekteve.",
"colorChangeTooltip": "Klikoni për të ndryshuar ngjyrën"
}

View File

@@ -0,0 +1,15 @@
{
"title": "Ndrysho Fjalëkalimin",
"currentPassword": "Fjalëkalimi Aktual",
"newPassword": "Fjalëkalimi i Ri",
"confirmPassword": "Konfirmo Fjalëkalimin",
"currentPasswordPlaceholder": "Vendosni fjalëkalimin aktual",
"newPasswordPlaceholder": "Fjalëkalimi i Ri",
"confirmPasswordPlaceholder": "Konfirmo Fjalëkalimin",
"currentPasswordRequired": "Ju lutemi vendosni fjalëkalimin aktual!",
"newPasswordRequired": "Ju lutemi vendosni fjalëkalimin e ri!",
"passwordValidationError": "Fjalëkalimi duhet të përmbajë të paktën 8 karaktere, me një shkronjë të madhe, një numër dhe një simbol.",
"passwordMismatch": "Fjalëkalimet nuk përputhen!",
"passwordRequirements": "Fjalëkalimi i ri duhet të jetë së paku 8 karaktere, me një shkronjë të madhe, një numër dhe një simbol.",
"updateButton": "Përditëso Fjalëkalimin"
}

View File

@@ -0,0 +1,22 @@
{
"nameColumn": "Emri",
"projectColumn": "Projekti",
"noProjectsAvailable": "Nuk ka projekte të disponueshme",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"searchPlaceholder": "Kërko sipas emrit",
"createClient": "Krijo Klient",
"pinTooltip": "Klikoni për ta fiksuar në menynë kryesore",
"createClientDrawerTitle": "Krijo Klient",
"updateClientDrawerTitle": "Përditëso Klientin",
"nameLabel": "Emri",
"namePlaceholder": "Emri",
"nameRequiredError": "Ju lutemi shkruani një Emër",
"createButton": "Krijo",
"updateButton": "Përditëso",
"createClientSuccessMessage": "Klienti u krijua me sukses!",
"createClientErrorMessage": "Krijimi i klientit dështoi!",
"updateClientSuccessMessage": "Klienti u përditësua me sukses!",
"updateClientErrorMessage": "Përditësimi i klientit dështoi!"
}

View File

@@ -0,0 +1,20 @@
{
"nameColumn": "Emri",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"searchPlaceholder": "Kërko sipas emrit",
"createJobTitleButton": "Krijo Titull Pune",
"pinTooltip": "Klikoni për ta fiksuar në menynë kryesore",
"createJobTitleDrawerTitle": "Krijo Titull Pune",
"updateJobTitleDrawerTitle": "Përditëso Titullin e Punës",
"nameLabel": "Emri",
"namePlaceholder": "Emri",
"nameRequiredError": "Ju lutemi shkruani një Emër",
"createButton": "Krijo",
"updateButton": "Përditëso",
"createJobTitleSuccessMessage": "Titulli i punës u krijua me sukses!",
"createJobTitleErrorMessage": "Krijimi i titullit të punës dështoi!",
"updateJobTitleSuccessMessage": "Titulli i punës u përditësua me sukses!",
"updateJobTitleErrorMessage": "Përditësimi i titullit të punës dështoi!"
}

View File

@@ -0,0 +1,11 @@
{
"labelColumn": "Etiketa",
"deleteConfirmationTitle": "Jeni i sigurt?",
"deleteConfirmationOk": "Po",
"deleteConfirmationCancel": "Anulo",
"associatedTaskColumn": "Numri i Detyrave të Lidhura",
"searchPlaceholder": "Kërko sipas emrit",
"emptyText": "Etiketat mund të krijohen gjatë përditësimit ose krijimit të detyrave.",
"pinTooltip": "Klikoni për ta fiksuar në menynë kryesore",
"colorChangeTooltip": "Klikoni për të ndryshuar ngjyrën"
}

View File

@@ -0,0 +1,7 @@
{
"language": "Gjuha",
"language_required": "Gjuha është e detyrueshme",
"time_zone": "Zona kohore",
"time_zone_required": "Zona kohore është e detyrueshme",
"save_changes": "Ruaj Ndryshimet"
}

View File

@@ -0,0 +1,11 @@
{
"title": "Cilësimet e Njoftimeve",
"emailTitle": "Më dërgo njoftime me email",
"emailDescription": "Kjo përfshin caktimet e reja të detyrave",
"dailyDigestTitle": "Më dërgo një përmbledhje ditore",
"dailyDigestDescription": "Çdo mbrëmje, do të merrni një përmbledhje të aktivitetit të fundit në detyra.",
"popupTitle": "Shfaq njoftimet në kompjuterin tim kur Worklenz është i hapur",
"popupDescription": "Njoftimet e shfaqura mund të çaktivizohen nga shfletuesi juaj. Ndryshoni cilësimet e shfletuesit për t'i lejuar ato.",
"unreadItemsTitle": "Shfaq numrin e artikujve të palexuar",
"unreadItemsDescription": "Do të shihni numërimin për çdo njoftim."
}

View File

@@ -0,0 +1,13 @@
{
"uploadError": "Mund të ngarkoni vetëm skedarë JPG/PNG!",
"uploadSizeError": "Imazhi duhet të jetë më i vogël se 2MB!",
"upload": "Ngarko",
"nameLabel": "Emri",
"nameRequiredError": "Emri është i detyrueshëm",
"emailLabel": "Email",
"emailRequiredError": "Email-i është i detyrueshëm",
"saveChanges": "Ruaj Ndryshimet",
"profileJoinedText": "U bashkua një muaj më parë",
"profileLastUpdatedText": "Përditësuar një muaj më parë",
"avatarTooltip": "Klikoni për të ngarkuar një avatar"
}

View File

@@ -0,0 +1,8 @@
{
"nameColumn": "Emri",
"editToolTip": "Modifiko",
"deleteToolTip": "Fshi",
"confirmText": "Jeni i sigurt?",
"okText": "Po",
"cancelText": "Anulo"
}

View File

@@ -0,0 +1,14 @@
{
"profile": "Profili",
"notifications": "Njoftimet",
"clients": "Klientët",
"job-titles": "Tituj Pune",
"labels": "Etiketa",
"categories": "Kategoritë",
"project-templates": "Shabllonet e Projekteve",
"task-templates": "Shabllonet e Detyrave",
"team-members": "Anëtarët e Ekipit",
"teams": "Ekipet",
"change-password": "Ndrysho Fjalëkalimin",
"language-and-region": "Gjuha dhe Rajoni"
}

View File

@@ -0,0 +1,9 @@
{
"nameColumn": "Emri",
"createdColumn": "Krijuar",
"editToolTip": "Redakto",
"deleteToolTip": "Fshi",
"confirmText": "Jeni i sigurt?",
"okText": "Po",
"cancelText": "Anulo"
}

View File

@@ -0,0 +1,44 @@
{
"nameColumn": "Emri",
"projectsColumn": "Projektet",
"emailColumn": "Email",
"teamAccessColumn": "Qasja në Ekip",
"memberCount": "Anëtar",
"membersCountPlural": "Anëtarë",
"searchPlaceholder": "Kërko anëtarë sipas emrit",
"pinTooltip": "Rifresko listën e anëtarëve",
"addMemberButton": "Shto Anëtar të Ri",
"editTooltip": "Modifiko anëtarin",
"deactivateTooltip": "Çaktivizo anëtarin",
"activateTooltip": "Aktivizo anëtarin",
"deleteTooltip": "Fshi anëtarin",
"confirmDeleteTitle": "Jeni i sigurt që doni të fshini këtë anëtar?",
"confirmActivateTitle": "Jeni i sigurt që doni të ndryshoni statusin e këtij anëtari?",
"okText": "Po, vazhdo",
"cancelText": "Jo, anulo",
"deactivatedText": "(Aktualisht i çaktivizuar)",
"pendingInvitationText": "(Ftesë në pritje)",
"addMemberDrawerTitle": "Shto Anëtar të Ri në Ekip",
"updateMemberDrawerTitle": "Përditëso Anëtarin e Ekipit",
"addMemberEmailHint": "Anëtarët do të shtohen në ekip pavarësisht nga statusi i pranimit të ftesës",
"memberEmailLabel": "Email(o)",
"memberEmailPlaceholder": "Vendos adresën email të anëtarit të ekipit",
"memberEmailRequiredError": "Ju lutemi vendosni një adresë email të vlefshme",
"jobTitleLabel": "Titulli i Punës",
"jobTitlePlaceholder": "Zgjidh ose kërko titull pune (Opsionale)",
"memberAccessLabel": "Niveli i Qasjes",
"addToTeamButton": "Shto Anëtar në Ekip",
"updateButton": "Ruaj Ndryshimet",
"resendInvitationButton": "Dërgo Përsëri Email-in e Ftesës",
"invitationSentSuccessMessage": "Ftesa për ekip u dërgua me sukses!",
"createMemberSuccessMessage": "Anëtari i ri i ekipit u shtua me sukses!",
"createMemberErrorMessage": "Dështoi shtimi i anëtarit të ri. Ju lutemi provoni përsëri.",
"updateMemberSuccessMessage": "Anëtari i ekipit u përditësua me sukses!",
"updateMemberErrorMessage": "Dështoi përditësimi i anëtarit. Ju lutemi provoni përsëri.",
"memberText": "Anëtar",
"adminText": "Administrues",
"ownerText": "Pronar i Ekipit",
"addedText": "Shtuar",
"updatedText": "Përditësuar",
"noResultFound": "Shkruani një adresë email dhe shtypni Enter..."
}

View File

@@ -0,0 +1,29 @@
{
"details": {
"task-key": "Çelësi i Detyrës",
"phase": "Faza",
"assignees": "Përgjegjësit",
"due-date": "Data e Përfundimit",
"time-estimation": "Vlerësimi i Kohës",
"priority": "Prioriteti",
"labels": "Etiketa",
"billable": "Fakturueshme",
"notify": "Njofto",
"when-done-notify": "Kur të përfundojë, njofto",
"start-date": "Data e Fillimit",
"end-date": "Data e Përfundimit",
"hide-start-date": "Fshih Datën e Fillimit",
"show-start-date": "Shfaq Datën e Fillimit",
"hours": "Orë",
"minutes": "Minuta"
},
"description": {
"title": "Përshkrimi",
"placeholder": "Shtoni një përshkrim më të detajuar..."
},
"subTasks": {
"title": "Nën-Detyrat",
"add-sub-task": "+ Shto Nën-Detyrë",
"refresh-sub-tasks": "Rifresko Nën-Detyrat"
}
}

View File

@@ -0,0 +1,78 @@
{
"taskHeader": {
"taskNamePlaceholder": "Shkruani detyrën tuaj",
"deleteTask": "Fshi Detyrën"
},
"taskInfoTab": {
"title": "Info",
"details": {
"title": "Detajet",
"task-key": "Çelësi i Detyrës",
"phase": "Faza",
"assignees": "Përgjegjësit",
"due-date": "Afati i Përfundimit",
"time-estimation": "Vlerësimi i Kohës",
"priority": "Prioriteti",
"labels": "Etiketa",
"billable": "Fakturueshme",
"notify": "Njofto",
"when-done-notify": "Kur të përfundojë, njofto",
"start-date": "Data e Fillimit",
"end-date": "Data e Përfundimit",
"hide-start-date": "Fshih Datën e Fillimit",
"show-start-date": "Shfaq Datën e Fillimit",
"hours": "Orë",
"minutes": "Minuta"
},
"labels": {
"labelInputPlaceholder": "Kërko ose krijo",
"labelsSelectorInputTip": "Shtyp Enter për të krijuar"
},
"description": {
"title": "Përshkrimi",
"placeholder": "Shtoni një përshkrim më të detajuar..."
},
"subTasks": {
"title": "Nën-Detyrat",
"addSubTask": "+ Shto Nën-Detyrë",
"addSubTaskInputPlaceholder": "Shkruani detyrën dhe shtypni Enter",
"refreshSubTasks": "Rifresko Nën-Detyrat",
"edit": "Modifiko",
"delete": "Fshi",
"confirmDeleteSubTask": "Jeni i sigurt që doni të fshini këtë nën-detyrë?",
"deleteSubTask": "Fshi Nën-Detyrën"
},
"dependencies": {
"title": "Varësitë",
"addDependency": "+ Shto varësi të re",
"blockedBy": "I bllokuar nga",
"searchTask": "Shkruani për të kërkuar detyra",
"noTasksFound": "Asnjë detyrë nuk u gjet",
"confirmDeleteDependency": "Jeni i sigurt që doni të fshini?"
},
"attachments": {
"title": "Bashkëngjitjet",
"chooseOrDropFileToUpload": "Zgjidhni ose lëshoni skedar për ngarkim",
"uploading": "Po ngarkohet..."
},
"comments": {
"title": "Komentet",
"addComment": "+ Shto koment të ri",
"noComments": "Asnjë koment ende. Bëhu i pari që komenton!",
"delete": "Fshi",
"confirmDeleteComment": "Jeni i sigurt që doni të fshini këtë koment?"
},
"searchInputPlaceholder": "Kërko sipas emrit",
"pendingInvitation": "Ftesë në Pritje"
},
"taskTimeLogTab": {
"title": "Regjistri i Kohës",
"addTimeLog": "Shto regjistrim të ri kohe",
"totalLogged": "Koha totale e regjistruar",
"exportToExcel": "Eksporto në Excel",
"noTimeLogsFound": "Asnjë regjistrim kohe nuk u gjet"
},
"taskActivityLogTab": {
"title": "Regjistri i Aktivitetit"
}
}

View File

@@ -0,0 +1,59 @@
{
"searchButton": "Kërko",
"resetButton": "Rivendos",
"searchInputPlaceholder": "Kërko sipas emrit",
"sortText": "Rendit",
"statusText": "Statusi",
"phaseText": "Faza",
"memberText": "Anëtarët",
"assigneesText": "Përgjegjësit",
"priorityText": "Prioriteti",
"labelsText": "Etiketa",
"membersText": "Anëtarët",
"groupByText": "Grupo sipas",
"showArchivedText": "Shfaq të arkivuara",
"showFieldsText": "Shfaq fushat",
"keyText": "Çelësi",
"taskText": "Detyra",
"descriptionText": "Përshkrimi",
"phasesText": "Fazat",
"listText": "Listë",
"progressText": "Progresi",
"timeTrackingText": "Gjurmimi i Kohës",
"timetrackingText": "Gjurmimi i Kohës",
"estimationText": "Vlerësimi",
"startDateText": "Data e Fillimit",
"startdateText": "Data e Fillimit",
"endDateText": "Data e Përfundimit",
"dueDateText": "Afati",
"duedateText": "Afati",
"completedDateText": "Data e Përfundimit",
"completeddateText": "Data e Përfundimit",
"createdDateText": "Data e Krijimit",
"createddateText": "Data e Krijimit",
"lastUpdatedText": "Përditësuar Së Fundi",
"lastupdatedText": "Përditësuar Së Fundi",
"reporterText": "Raportuesi",
"dueTimeText": "Koha e Afatit",
"duetimeText": "Koha e Afatit",
"lowText": "I ulët",
"mediumText": "I mesëm",
"highText": "I lartë",
"createStatusButtonTooltip": "Cilësimet e statusit",
"configPhaseButtonTooltip": "Cilësimet e fazës",
"noLabelsFound": "Nuk u gjetën etiketa",
"addStatusButton": "Shto Status",
"addPhaseButton": "Shto Fazë",
"createStatus": "Krijo Status",
"name": "Emri",
"category": "Kategoria",
"selectCategory": "Zgjidh një kategori",
"pleaseEnterAName": "Ju lutemi vendosni një emër",
"pleaseSelectACategory": "Ju lutemi zgjidhni një kategori",
"create": "Krijo"
}

View File

@@ -0,0 +1,63 @@
{
"keyColumn": "Çelësi",
"taskColumn": "Detyra",
"descriptionColumn": "Përshkrimi",
"progressColumn": "Progresi",
"membersColumn": "Anëtarët",
"assigneesColumn": "Përgjegjësit",
"labelsColumn": "Etiketa",
"phasesColumn": "Fazat",
"phaseColumn": "Faza",
"statusColumn": "Statusi",
"priorityColumn": "Prioriteti",
"timeTrackingColumn": "Gjurmimi i Kohës",
"timetrackingColumn": "Gjurmimi i Kohës",
"estimationColumn": "Vlerësimi",
"startDateColumn": "Data e Fillimit",
"startdateColumn": "Data e Fillimit",
"dueDateColumn": "Data e Afatit",
"duedateColumn": "Data e Afatit",
"completedDateColumn": "Data e Përfundimit",
"completeddateColumn": "Data e Përfundimit",
"createdDateColumn": "Data e Krijimit",
"createddateColumn": "Data e Krijimit",
"lastUpdatedColumn": "Përditësuar Së Fundi",
"lastupdatedColumn": "Përditësuar Së Fundi",
"reporterColumn": "Raportuesi",
"dueTimeColumn": "Koha e Afatit",
"todoSelectorText": "Për të Bërë",
"doingSelectorText": "Duke bërë",
"doneSelectorText": "E Përfunduar",
"lowSelectorText": "I ulët",
"mediumSelectorText": "I mesëm",
"highSelectorText": "I lartë",
"selectText": "Zgjidh",
"labelsSelectorInputTip": "Shtyp Enter për të krijuar!",
"addTaskText": "+ Shto Detyrë",
"addSubTaskText": "+ Shto Nën-Detyrë",
"addTaskInputPlaceholder": "Shkruaj detyrën dhe shtyp Enter",
"openButton": "Hap",
"okButton": "Në rregull",
"noLabelsFound": "Nuk u gjetën etiketa",
"searchInputPlaceholder": "Kërko ose krijo",
"assigneeSelectorInviteButton": "Fto një anëtar të ri me email",
"labelInputPlaceholder": "Kërko ose krijo",
"pendingInvitation": "Ftesë në Pritje",
"contextMenu": {
"assignToMe": "Cakto mua",
"moveTo": "Zhvendos në",
"unarchive": "Ç'arkivizo",
"archive": "Arkivizo",
"convertToSubTask": "Shndërro në Nën-Detyrë",
"convertToTask": "Shndërro në Detyrë",
"delete": "Fshi",
"searchByNameInputPlaceholder": "Kërko sipas emrit"
}
}

View File

@@ -0,0 +1,11 @@
{
"createTaskTemplate": "Krijo Shabllon Detyre",
"editTaskTemplate": "Modifiko Shabllon Detyre",
"cancelText": "Anulo",
"saveText": "Ruaj",
"templateNameText": "Emri i Shabllonit",
"selectedTasks": "Detyrat e Përzgjedhura",
"removeTask": "Hiq",
"cancelButton": "Anulo",
"saveButton": "Ruaj"
}

View File

@@ -0,0 +1,24 @@
{
"taskSelected": "detyrë e zgjedhur",
"tasksSelected": "detyra të zgjedhura",
"changeStatus": "Ndrysho Statusin/ Prioritetin/ Fazat",
"changeLabel": "Ndrysho Etiketën",
"assignToMe": "Cakto mua",
"changeAssignees": "Ndrysho Përgjegjësit",
"archive": "Arkivo",
"unarchive": "Ç'arkivo",
"delete": "Fshi",
"moreOptions": "Më shumë opsione",
"deselectAll": "Zgjidhja të gjitha",
"status": "Statusi",
"priority": "Prioriteti",
"phase": "Faza",
"member": "Anëtar",
"createTaskTemplate": "Krijo Shabllon Detyre",
"apply": "Apliko",
"createLabel": "+ Krijo Etiketë",
"hitEnterToCreate": "Shtyp Enter për të krijuar",
"pendingInvitation": "Ftesë në Pritje",
"noMatchingLabels": "Asnjë etiketë që përputhet",
"noLabels": "Asnjë etiketë"
}

View File

@@ -0,0 +1,19 @@
{
"title": "Modifiko Shabllon Detyre",
"cancelText": "Anulo",
"saveText": "Ruaj",
"templateNameText": "Emri i Shabllonit",
"selectedTasks": "Detyrat e Përzgjedhura",
"removeTask": "Hiq",
"description": "Përshkrimi",
"phase": "Faza",
"statuses": "Statuset",
"priorities": "Prioritetet",
"labels": "Etiketa",
"tasks": "Detyrat",
"noTemplateSelected": "Asnjë shabllon i përzgjedhur",
"noDescription": "Pa përshkrim",
"worklenzTemplates": "Shabllonet Worklenz",
"yourTemplatesLibrary": "Biblioteka Juaj",
"searchTemplates": "Kërko Shabllone"
}

View File

@@ -0,0 +1,23 @@
{
"bugTracking": "Gjurmimi i Gabimeve",
"construction": "Ndërtim",
"designCreative": "Dizajn & Kreativ",
"education": "Arsim",
"finance": "Financë",
"hrRecruiting": "Burime Njerëzore & Rekrutim",
"informationTechnology": "Teknologji Informacioni",
"legal": "Juridik",
"manufacturing": "Prodhim",
"marketing": "Marketing",
"nonprofit": "Jo-fitimprurës",
"personalUse": "Përdorim Personal",
"salesCRM": "Shitje & CRM",
"serviceConsulting": "Shërbime & Këshillim",
"softwareDevelopment": "Zhvillim Softueri",
"description": "Përshkrimi",
"phase": "Faza",
"statuses": "Statuset",
"priorities": "Prioritetet",
"labels": "Etiketa",
"tasks": "Detyrat"
}

View File

@@ -0,0 +1,44 @@
{
"includeArchivedProjects": "Përfshij Projektet e Arkivuara",
"export": "Eksporto",
"timeSheet": "Fletë Kohore",
"searchByName": "Kërko sipas emrit",
"selectAll": "Zgjidh të Gjitha",
"teams": "Ekipet",
"searchByProject": "Kërko sipas emrit të projektit",
"projects": "Projektet",
"searchByCategory": "Kërko sipas emrit të kategorisë",
"categories": "Kategoritë",
"billable": "Fakturueshme",
"nonBillable": "Jo Fakturueshme",
"total": "Total",
"projectsTimeSheet": "Fletë Kohore e Projekteve",
"loggedTime": "Koha e Regjistruar(orë)",
"exportToExcel": "Eksporto në Excel",
"logged": "regjistruar",
"for": "për",
"membersTimeSheet": "Fletë Kohore e Anëtarëve",
"member": "Anëtar",
"estimatedVsActual": "Vlerësuar vs Aktual",
"workingDays": "Ditë Pune",
"manDays": "Ditë Njeri",
"days": "Ditë",
"estimatedDays": "Ditë të Vlerësuara",
"actualDays": "Ditë Aktuale",
"noCategories": "Nuk u gjetën kategori",
"noCategory": "Pa Kategori",
"noProjects": "Nuk u gjetën projekte",
"noTeams": "Nuk u gjetën ekipe",
"noData": "Nuk u gjetën të dhëna"
}

View File

@@ -0,0 +1,5 @@
{
"title": "E paautorizuar!",
"subtitle": "Nuk jeni të autorizuar të hyni në këtë faqe",
"button": "Kthehu në Faqen Kryesore"
}

View File

@@ -0,0 +1,4 @@
{
"doesNotExistText": "Entschuldigung, die von Ihnen besuchte Seite existiert nicht.",
"backHomeButton": "Zurück zur Startseite"
}

View File

@@ -0,0 +1,31 @@
{
"continue": "Weiter",
"setupYourAccount": "Richten Sie Ihr Worklenz-Konto ein.",
"organizationStepTitle": "Organisation benennen",
"organizationStepLabel": "Wählen Sie einen Namen für Ihr Worklenz-Konto.",
"projectStepTitle": "Erstellen Sie Ihr erstes Projekt",
"projectStepLabel": "An welchem Projekt arbeiten Sie gerade?",
"projectStepPlaceholder": "z.B. Marketingplan",
"tasksStepTitle": "Erstellen Sie Ihre ersten Aufgaben",
"tasksStepLabel": "Geben Sie einige Aufgaben ein, die Sie in",
"tasksStepAddAnother": "Weitere hinzufügen",
"emailPlaceholder": "E-Mail-Adresse",
"invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"or": "oder",
"templateButton": "Aus Vorlage importieren",
"goBack": "Zurück",
"cancel": "Abbrechen",
"create": "Erstellen",
"templateDrawerTitle": "Aus Vorlagen auswählen",
"step3InputLabel": "Per E-Mail einladen",
"addAnother": "Weitere hinzufügen",
"skipForNow": "Jetzt überspringen",
"formTitle": "Erstellen Sie Ihre erste Aufgabe.",
"step3Title": "Laden Sie Ihr Team zur Zusammenarbeit ein",
"maxMembers": " (Sie können bis zu 5 Mitglieder einladen)",
"maxTasks": " (Sie können bis zu 5 Aufgaben erstellen)"
}

View File

@@ -0,0 +1,113 @@
{
"title": "Abrechnungen",
"currentBill": "Aktuelle Rechnung",
"configuration": "Konfiguration",
"currentPlanDetails": "Aktuelle Plan Details",
"upgradePlan": "Plan upgraden",
"cardBodyText01": "Kostenlose Testversion",
"cardBodyText02": "(Ihr Testplan läuft in 1 Monat 19 Tagen ab)",
"redeemCode": "Gutscheincode einlösen",
"accountStorage": "Kontospeicher",
"used": "Verwendet:",
"remaining": "Verbleibend:",
"charges": "Gebühren",
"tooltip": "Gebühren für den aktuellen Abrechnungszeitraum",
"description": "Beschreibung",
"billingPeriod": "Abrechnungszeitraum",
"billStatus": "Rechnungsstatus",
"perUserValue": "Pro Benutzer Wert",
"users": "Benutzer",
"amount": "Betrag",
"invoices": "Rechnungen",
"transactionId": "Transaktions-ID",
"transactionDate": "Transaktionsdatum",
"paymentMethod": "Zahlungsmethode",
"status": "Status",
"ltdUsers": "Sie können bis zu {{ltd_users}} Benutzer hinzufügen.",
"totalSeats": "Gesamte Plätze",
"availableSeats": "Verfügbare Plätze",
"addMoreSeats": "Weitere Plätze hinzufügen",
"drawerTitle": "Gutscheincode einlösen",
"label": "Gutscheincode",
"drawerPlaceholder": "Geben Sie Ihren Gutscheincode ein",
"redeemSubmit": "Einreichen",
"modalTitle": "Wählen Sie den besten Plan für Ihr Team",
"seatLabel": "Anzahl der Plätze",
"freePlan": "Kostenloser Plan",
"startup": "Startup",
"business": "Business",
"tag": "Am beliebtesten",
"enterprise": "Enterprise",
"freeSubtitle": "kostenlos für immer",
"freeUsers": "Ideal für die persönliche Nutzung",
"freeText01": "100MB Speicher",
"freeText02": "3 Projekte",
"freeText03": "5 Teammitglieder",
"startupSubtitle": "PAUSCHALPREIS / Monat",
"startupUsers": "Bis zu 15 Benutzer",
"startupText01": "25GB Speicher",
"startupText02": "Unbegrenzte aktive Projekte",
"startupText03": "Zeitplan",
"startupText04": "Berichterstattung",
"startupText05": "Projekte abonnieren",
"businessSubtitle": "Benutzer / Monat",
"businessUsers": "16 - 200 Benutzer",
"enterpriseUsers": "200 - 500+ Benutzer",
"footerTitle": "Bitte geben Sie uns eine Kontaktnummer, unter der wir Sie erreichen können.",
"footerLabel": "Kontaktnummer",
"footerButton": "Kontaktieren Sie uns",
"redeemCodePlaceHolder": "Geben Sie Ihren Gutscheincode ein",
"submit": "Einreichen",
"trialPlan": "Kostenlose Testversion",
"trialExpireDate": "Gültig bis {{trial_expire_date}}",
"trialExpired": "Ihre kostenlose Testversion ist {{trial_expire_string}} abgelaufen",
"trialInProgress": "Ihre kostenlose Testversion läuft {{trial_expire_string}} ab",
"required": "Dieses Feld ist erforderlich",
"invalidCode": "Ungültiger Code",
"selectPlan": "Wählen Sie den besten Plan für Ihr Team",
"changeSubscriptionPlan": "Ändern Sie Ihren Abonnementplan",
"noOfSeats": "Anzahl der Plätze",
"annualPlan": "Pro - Jährlich",
"monthlyPlan": "Pro - Monatlich",
"freeForever": "Kostenlos für immer",
"bestForPersonalUse": "Ideal für die persönliche Nutzung",
"storage": "Speicher",
"projects": "Projekte",
"teamMembers": "Teammitglieder",
"unlimitedTeamMembers": "Unbegrenzte Teammitglieder",
"unlimitedActiveProjects": "Unbegrenzte aktive Projekte",
"schedule": "Zeitplan",
"reporting": "Berichterstattung",
"subscribeToProjects": "Projekte abonnieren",
"billedAnnually": "Jährlich abgerechnet",
"billedMonthly": "Monatlich abgerechnet",
"pausePlan": "Plan pausieren",
"resumePlan": "Plan fortsetzen",
"changePlan": "Plan ändern",
"cancelPlan": "Plan kündigen",
"perMonthPerUser": "pro Benutzer/Monat",
"viewInvoice": "Rechnung anzeigen",
"switchToFreePlan": "Wechsel zum kostenlosen Plan",
"expirestoday": "heute",
"expirestomorrow": "morgen",
"expiredDaysAgo": "vor {{days}} Tagen",
"continueWith": "Fortfahren mit {{plan}}",
"changeToPlan": "Wechseln zu {{plan}}"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Übersicht",
"name": "Organisationsname",
"owner": "Organisationsinhaber",
"admins": "Organisationsadministratoren",
"contactNumber": "Kontaktnummer hinzufügen",
"edit": "Bearbeiten"
}

View File

@@ -0,0 +1,12 @@
{
"membersCount": "Mitgliederanzahl",
"createdAt": "Erstellt am",
"projectName": "Projektname",
"teamName": "Teamname",
"refreshProjects": "Projekte aktualisieren",
"searchPlaceholder": "Nach Projektname suchen",
"deleteProject": "Sind Sie sicher, dass Sie dieses Projekt löschen möchten?",
"confirm": "Bestätigen",
"cancel": "Abbrechen",
"delete": "Projekt löschen"
}

View File

@@ -0,0 +1,8 @@
{
"overview": "Übersicht",
"users": "Benutzer",
"teams": "Teams",
"billing": "Abrechnung",
"projects": "Projekte",
"adminCenter": "Admin-Center"
}

View File

@@ -0,0 +1,33 @@
{
"title": "Teams",
"subtitle": "Teams",
"tooltip": "Teams aktualisieren",
"placeholder": "Nach Namen suchen",
"addTeam": "Team hinzufügen",
"team": "Team",
"membersCount": "Mitgliederanzahl",
"members": "Mitglieder",
"drawerTitle": "Neues Team erstellen",
"label": "Teamname",
"drawerPlaceholder": "Name",
"create": "Erstellen",
"delete": "Löschen",
"settings": "Einstellungen",
"popTitle": "Sind Sie sicher?",
"message": "Bitte geben Sie einen Namen ein",
"teamSettings": "Team-Einstellungen",
"teamName": "Teamname",
"teamDescription": "Teambeschreibung",
"teamMembers": "Teammitglieder",
"teamMembersCount": "Anzahl der Teammitglieder",
"teamMembersPlaceholder": "Nach Namen suchen",
"addMember": "Mitglied hinzufügen",
"add": "Hinzufügen",
"update": "Aktualisieren",
"teamNamePlaceholder": "Name des Teams",
"user": "Benutzer",
"role": "Rolle",
"owner": "Besitzer",
"admin": "Administrator",
"member": "Mitglied"
}

View File

@@ -0,0 +1,9 @@
{
"title": "Benutzer",
"subTitle": "Benutzer",
"placeholder": "Nach Namen suchen",
"user": "Benutzer",
"email": "E-Mail",
"lastActivity": "Letzte Aktivität",
"refresh": "Benutzer aktualisieren"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Name",
"client": "Kunde",
"category": "Kategorie",
"status": "Status",
"tasksProgress": "Aufgabenfortschritt",
"updated_at": "Zuletzt aktualisiert",
"members": "Mitglieder",
"setting": "Einstellungen",
"projects": "Projekte",
"refreshProjects": "Projekte aktualisieren",
"all": "Alle",
"favorites": "Favoriten",
"archived": "Archiviert",
"placeholder": "Nach Namen suchen",
"archive": "Archivieren",
"unarchive": "Dearchivieren",
"archiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt archivieren möchten?",
"unarchiveConfirm": "Sind Sie sicher, dass Sie dieses Projekt dearchivieren möchten?",
"clickToFilter": "Zum Filtern klicken nach",
"noProjects": "Keine Projekte gefunden",
"addToFavourites": "Zu Favoriten hinzufügen"
}

View File

@@ -0,0 +1,5 @@
{
"loggingOut": "Abmelden...",
"authenticating": "Authentifizierung läuft...",
"gettingThingsReady": "Bereite alles für Sie vor..."
}

View File

@@ -0,0 +1,12 @@
{
"headerDescription": "Passwort zurücksetzen",
"emailLabel": "E-Mail",
"emailPlaceholder": "Ihre E-Mail eingeben",
"emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!",
"resetPasswordButton": "Passwort zurücksetzen",
"returnToLoginButton": "Zurück zum Login",
"passwordResetSuccessMessage": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail gesendet.",
"orText": "ODER",
"successTitle": "Anweisung zum Zurücksetzen gesendet!",
"successMessage": "Die Informationen zum Zurücksetzen wurden an Ihre E-Mail gesendet. Bitte überprüfen Sie Ihr E-Mail-Postfach."
}

View File

@@ -0,0 +1,27 @@
{
"headerDescription": "Melden Sie sich an",
"emailLabel": "E-Mail",
"emailPlaceholder": "Ihre E-Mail-Adresse eingeben",
"emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!",
"passwordLabel": "Passwort",
"passwordPlaceholder": "Ihr Passwort eingeben",
"passwordRequired": "Bitte geben Sie Ihr Passwort ein!",
"rememberMe": "Erinnere dich an mich",
"loginButton": "Anmelden",
"signupButton": "Registrieren",
"forgotPasswordButton": "Passwort vergessen?",
"signInWithGoogleButton": "Mit Google anmelden",
"dontHaveAccountText": "Noch kein Konto?",
"orText": "ODER",
"successMessage": "Sie haben sich erfolgreich angemeldet!",
"loginError": "Anmeldung fehlgeschlagen",
"googleLoginError": "Google-Anmeldung fehlgeschlagen",
"validationMessages": {
"email": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"password": "Das Passwort muss mindestens 8 Zeichen lang sein"
},
"errorMessages": {
"loginErrorTitle": "Anmeldung fehlgeschlagen",
"loginErrorMessage": "Bitte überprüfen Sie Ihre E-Mail-Adresse und Ihr Passwort und versuchen Sie es erneut"
}
}

View File

@@ -0,0 +1,29 @@
{
"headerDescription": "Registrieren Sie sich, um loszulegen",
"nameLabel": "Vollständiger Name",
"namePlaceholder": "Ihren vollständigen Namen eingeben",
"nameRequired": "Bitte geben Sie Ihren vollständigen Namen ein!",
"nameMinCharacterRequired": "Der Name muss mindestens 4 Zeichen lang sein!",
"emailLabel": "E-Mail",
"emailPlaceholder": "Ihre E-Mail-Adresse eingeben",
"emailRequired": "Bitte geben Sie Ihre E-Mail-Adresse ein!",
"passwordLabel": "Passwort",
"passwordPlaceholder": "Ihr Passwort eingeben",
"passwordRequired": "Bitte geben Sie Ihr Passwort ein!",
"passwordMinCharacterRequired": "Das Passwort muss mindestens 8 Zeichen lang sein!",
"passwordPatternRequired": "Das Passwort erfüllt nicht die Anforderungen!",
"strongPasswordPlaceholder": "Ein stärkeres Passwort eingeben",
"passwordValidationAltText": "Das Passwort muss mindestens 8 Zeichen enthalten, mit Groß- und Kleinbuchstaben, einer Zahl und einem Sonderzeichen.",
"signupSuccessMessage": "Sie haben sich erfolgreich registriert!",
"privacyPolicyLink": "Datenschutzrichtlinie",
"termsOfUseLink": "Nutzungsbedingungen",
"bySigningUpText": "Mit der Registrierung stimmen Sie unseren",
"andText": "und",
"signupButton": "Registrieren",
"signInWithGoogleButton": "Mit Google anmelden",
"alreadyHaveAccountText": "Sie haben bereits ein Konto?",
"loginButton": "Anmelden",
"orText": "ODER",
"reCAPTCHAVerificationError": "reCAPTCHA-Verifizierungsfehler",
"reCAPTCHAVerificationErrorMessage": "Wir konnten Ihre reCAPTCHA nicht verifizieren. Bitte versuchen Sie es erneut."
}

View File

@@ -0,0 +1,14 @@
{
"title": "E-Mail zurücksetzen bestätigen",
"description": "Geben Sie Ihr neues Passwort ein",
"placeholder": "Neues Passwort eingeben",
"confirmPasswordPlaceholder": "Neues Passwort bestätigen",
"passwordHint": "Mindestens 8 Zeichen, mit Groß- und Kleinbuchstaben, einer Zahl und einem Sonderzeichen.",
"resetPasswordButton": "Passwort zurücksetzen",
"orText": "Oder",
"resendResetEmail": "Zurücksetz-E-Mail erneut senden",
"passwordRequired": "Bitte geben Sie Ihr neues Passwort ein",
"returnToLoginButton": "Zurück zur Anmeldung",
"confirmPasswordRequired": "Bitte bestätigen Sie Ihr neues Passwort",
"passwordMismatch": "Die beiden Passwörter stimmen nicht überein"
}

View File

@@ -0,0 +1,9 @@
{
"login-success": "Anmeldung erfolgreich!",
"login-failed": "Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten und versuchen Sie es erneut.",
"signup-success": "Registrierung erfolgreich! Willkommen an Bord.",
"signup-failed": "Registrierung fehlgeschlagen. Bitte füllen Sie alle erforderlichen Felder aus und versuchen Sie es erneut.",
"reconnecting": "Vom Server getrennt.",
"connection-lost": "Verbindung zum Server fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.",
"connection-restored": "Erfolgreich mit dem Server verbunden"
}

View File

@@ -0,0 +1,13 @@
{
"formTitle": "Erstellen Sie Ihr erstes Projekt",
"inputLabel": "An welchem Projekt arbeiten Sie gerade?",
"or": "oder",
"templateButton": "Aus Vorlage importieren",
"createFromTemplate": "Aus Vorlage erstellen",
"goBack": "Zurück",
"continue": "Weitermachen",
"cancel": "Abbrechen",
"create": "Erstellen",
"templateDrawerTitle": "Aus Vorlagen auswählen",
"createProject": "Projekt erstellen"
}

View File

@@ -0,0 +1,7 @@
{
"formTitle": "Erstellen Sie Ihre erste Aufgabe.",
"inputLabel": "Geben Sie einige Aufgaben ein, die Sie erledigen werden in",
"addAnother": "Einen weiteren hinzufügen",
"goBack": "Zurück",
"continue": "Weiter"
}

View File

@@ -0,0 +1,46 @@
{
"todoList": {
"title": "Aufgabenliste",
"refreshTasks": "Aufgaben aktualisieren",
"addTask": "+ Aufgabe hinzufügen",
"noTasks": "Keine Aufgaben",
"pressEnter": "Drücken Sie",
"toCreate": "zum Erstellen.",
"markAsDone": "Als erledigt markieren"
},
"projects": {
"title": "Projekte",
"refreshProjects": "Projekte aktualisieren",
"noRecentProjects": "Sie sind aktuell keinem Projekt zugewiesen.",
"noFavouriteProjects": "Keine Projekte als Favoriten markiert.",
"recent": "Kürzlich",
"favourites": "Favoriten"
},
"tasks": {
"assignedToMe": "Mir zugewiesen",
"assignedByMe": "Von mir zugewiesen",
"all": "Alle",
"today": "Heute",
"upcoming": "Bevorstehend",
"overdue": "Überfällig",
"noDueDate": "Kein Fälligkeitsdatum",
"noTasks": "Keine Aufgaben zum Anzeigen.",
"addTask": "+ Aufgabe hinzufügen",
"name": "Name",
"project": "Projekt",
"status": "Status",
"dueDate": "Fälligkeitsdatum",
"dueDatePlaceholder": "Fälligkeitsdatum festlegen",
"tomorrow": "Morgen",
"nextWeek": "Nächste Woche",
"nextMonth": "Nächster Monat",
"projectRequired": "Bitte wählen Sie ein Projekt aus",
"pressTabToSelectDueDateAndProject": "Drücken Sie Tab, um ein Fälligkeitsdatum und ein Projekt auszuwählen",
"dueOn": "Fällige Aufgaben am",
"taskRequired": "Bitte fügen Sie eine Aufgabe hinzu",
"list": "Liste",
"calendar": "Kalender",
"tasks": "Aufgaben",
"refresh": "Aktualisieren"
}
}

View File

@@ -0,0 +1,8 @@
{
"formTitle": "Laden Sie Ihr Team zur Zusammenarbeit ein",
"inputLabel": "Per E-Mail einladen",
"addAnother": "Weitere hinzufügen",
"goBack": "Zurück",
"continue": "Weitermachen",
"skipForNow": "Vorerst überspringen"
}

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