@@ -324,8 +378,9 @@ function CalendarEditor({ cal, countries, onUpdate, onDelete }: {
)
}
-function AddCalendarDraft({ countries, onAdd, onCancel }: {
+function AddCalendarDraft({ countries, calendarType = 'public_holiday', onAdd, onCancel }: {
countries: Option[]
+ calendarType?: 'public_holiday' | 'school_holiday'
onAdd: (data: { region: string; color: string; label: string | null }) => void
onCancel: () => void
}) {
@@ -340,8 +395,9 @@ function AddCalendarDraft({ countries, onAdd, onCancel }: {
useEffect(() => {
if (!country) { setRegions([]); return }
- fetchRegionOptions(country).then(setRegions)
- }, [country])
+ const load = calendarType === 'school_holiday' ? fetchSchoolHolidayRegionOptions : fetchRegionOptions
+ load(country).then(setRegions)
+ }, [country, calendarType])
const canAdd = Boolean(country) && (regions.length === 0 || selectedRegion !== '')
diff --git a/client/src/mobile/screens/vacay/vacayDayModel.ts b/client/src/mobile/screens/vacay/vacayDayModel.ts
index 624a631b8..3d4d9951b 100644
--- a/client/src/mobile/screens/vacay/vacayDayModel.ts
+++ b/client/src/mobile/screens/vacay/vacayDayModel.ts
@@ -1,3 +1,4 @@
+import { schoolHolidayWash } from '../../../components/Vacay/holidayVisual'
import type { HolidaysMap, VacayEntry } from '../../../types'
// Users created before picking a color have color=null; same fallback the
@@ -10,6 +11,9 @@ export interface DayVisual {
boxShadow?: string
// At least one person logged this day as a half day (#552) — the cell shows a ½ badge.
half?: boolean
+ // Colours of the school-holiday calendars covering this day — drawn as a rounded
+ // accent band under the number, on top of whatever fill the cell already has.
+ school?: string[]
}
export interface DayVisualContext {
@@ -84,11 +88,19 @@ export function splitBackground(colors: string[]): string {
* theme-independent surfaces.
*/
function baseDayVisual(dateStr: string, dayOfWeek: number, ctx: DayVisualContext): DayVisual {
+ const holidayMarkers = ctx.holidays[dateStr]
+ const holidays = Array.isArray(holidayMarkers) ? holidayMarkers : holidayMarkers ? [holidayMarkers] : []
+ const publicHoliday = holidays.find(holiday => (holiday.type ?? 'public_holiday') === 'public_holiday')
+ const schoolColors = holidays.filter(holiday => holiday.type === 'school_holiday').map(holiday => holiday.color)
+ // The school band sits on top of any fill (person / company / public), so tag it
+ // onto whatever visual this day resolves to rather than picking a single winner.
+ const withSchool = (v: DayVisual): DayVisual => (schoolColors.length > 0 ? { ...v, school: schoolColors } : v)
+
if (dateStr === ctx.todayStr) {
- return { background: 'transparent', numColor: 'var(--m-ink)', boxShadow: 'inset 0 0 0 1.5px var(--m-ink)' }
+ return withSchool({ background: 'transparent', numColor: 'var(--m-ink)', boxShadow: 'inset 0 0 0 1.5px var(--m-ink)' })
}
if (ctx.companyHolidaysEnabled && ctx.companyHolidaySet.has(dateStr)) {
- return { background: '#F5D9A6', numColor: '#8A5A00' }
+ return withSchool({ background: '#F5D9A6', numColor: '#8A5A00' })
}
const entries = ctx.entryMap[dateStr]
if (entries && entries.length > 0) {
@@ -98,11 +110,14 @@ function baseDayVisual(dateStr: string, dayOfWeek: number, ctx: DayVisualContext
// true so full days keep their exact { background, numColor } shape.
const visual: DayVisual = { background: tints.length === 1 ? tints[0] : splitBackground(tints), numColor: '#101013' }
if (entries.some(e => (e.fraction ?? 1) === 0.5)) visual.half = true
- return visual
+ return withSchool(visual)
}
- const holiday = ctx.holidays[dateStr]
- if (holiday) {
- return { background: holiday.color, numColor: holidayInk(holiday.color) }
+ if (publicHoliday) {
+ return withSchool({ background: publicHoliday.color, numColor: holidayInk(publicHoliday.color) })
+ }
+ // Plain school-break day: soft wash + readable tinted ink, matching the desktop card.
+ if (schoolColors.length > 0) {
+ return { background: schoolHolidayWash(schoolColors[0]), numColor: holidayInk(schoolColors[0]), school: schoolColors }
}
if (ctx.weekendDays.includes(dayOfWeek)) {
return { background: 'var(--m-ic)', numColor: 'var(--m-faint)' }
diff --git a/client/src/pages/VacayPage.tsx b/client/src/pages/VacayPage.tsx
index 5b7337346..09e05660b 100644
--- a/client/src/pages/VacayPage.tsx
+++ b/client/src/pages/VacayPage.tsx
@@ -94,14 +94,17 @@ function VacayPageDesktop(): React.ReactElement {
{/* Legend */}
- {(plan?.holidays_enabled || plan?.company_holidays_enabled || plan?.block_weekends || hasVisibleShared) && (
+ {(plan?.holidays_enabled || plan?.school_holidays_enabled || plan?.company_holidays_enabled || plan?.block_weekends || hasVisibleShared) && (
{t('vacay.legend')}
- {plan?.holidays_enabled && (plan?.holiday_calendars ?? []).length === 0 && (
+ {plan?.holidays_enabled && (plan?.holiday_calendars ?? []).filter(cal => (cal.type ?? 'public_holiday') === 'public_holiday').length === 0 && (
)}
- {plan?.holidays_enabled && (plan?.holiday_calendars ?? []).map(cal => (
+ {plan?.holidays_enabled && (plan?.holiday_calendars ?? []).filter(cal => (cal.type ?? 'public_holiday') === 'public_holiday').map(cal => (
+
+ ))}
+ {plan?.school_holidays_enabled && (plan?.holiday_calendars ?? []).filter(cal => cal.type === 'school_holiday').map(cal => (
))}
{plan?.company_holidays_enabled && }
diff --git a/client/src/store/vacayStore.ts b/client/src/store/vacayStore.ts
index 1a83a18c8..fcae85df7 100644
--- a/client/src/store/vacayStore.ts
+++ b/client/src/store/vacayStore.ts
@@ -6,6 +6,7 @@ import type {
VacayPlan, VacayUser, VacayEntry, VacayStat, HolidaysMap, HolidayInfo, VacayHolidayCalendar,
VacayShareOutgoing, VacayShareIncoming, SharedVacayCalendar,
} from '../types'
+import { isSchoolHolidayCountrySupported } from '../vacay/schoolHolidayCountries'
import type {
VacaySetColorRequest, VacayInviteRequest, VacayInviteActionRequest,
VacayAddYearRequest, VacayToggleEntryRequest, VacayCompanyHolidayRequest,
@@ -54,6 +55,12 @@ interface VacayHolidayRaw {
counties: string[] | null
}
+interface VacaySchoolHolidayRaw {
+ startDate?: string
+ endDate?: string
+ name?: string | { language?: string; text?: string }[]
+}
+
interface VacayApi {
getPlan: () => Promise
updatePlan: (data: Partial) => Promise<{ plan: VacayPlan }>
@@ -74,8 +81,9 @@ interface VacayApi {
updateStats: (year: number, days: number, targetUserId?: number) => Promise
getCountries: () => Promise<{ countries: string[] }>
getHolidays: (year: number, country: string) => Promise
- addHolidayCalendar: (data: { region: string; color?: string; label?: string | null }) => Promise<{ calendar: VacayHolidayCalendar }>
- updateHolidayCalendar: (id: number, data: { region?: string; color?: string; label?: string | null }) => Promise<{ calendar: VacayHolidayCalendar }>
+ getSchoolHolidays: (year: number, country: string, subdivision?: string | null, group?: string | null) => Promise
+ addHolidayCalendar: (data: { region: string; color?: string; label?: string | null; type?: 'public_holiday' | 'school_holiday' }) => Promise<{ calendar: VacayHolidayCalendar }>
+ updateHolidayCalendar: (id: number, data: { region?: string; color?: string; label?: string | null; type?: 'public_holiday' | 'school_holiday' }) => Promise<{ calendar: VacayHolidayCalendar }>
deleteHolidayCalendar: (id: number) => Promise
getShares: () => Promise<{ outgoing: VacayShareOutgoing[]; incoming: VacayShareIncoming[] }>
share: (userId: number) => Promise
@@ -105,6 +113,12 @@ const api: VacayApi = {
updateStats: (year, days, targetUserId) => ax.put(`/addons/vacay/stats/${year}`, { vacation_days: days, target_user_id: targetUserId } satisfies VacayUpdateStatsRequest).then((r: AxiosResponse) => r.data),
getCountries: () => ax.get('/addons/vacay/holidays/countries').then((r: AxiosResponse) => r.data),
getHolidays: (year, country) => ax.get(`/addons/vacay/holidays/${year}/${country}`).then((r: AxiosResponse) => r.data),
+ getSchoolHolidays: (year, country, subdivision, group) => {
+ const params = new URLSearchParams()
+ if (group) params.set('group', group)
+ const qs = params.toString()
+ return ax.get(`/addons/vacay/school-holidays/${year}/${country}${subdivision ? `/${subdivision}` : ''}${qs ? `?${qs}` : ''}`).then((r: AxiosResponse) => r.data)
+ },
addHolidayCalendar: (data) => ax.post('/addons/vacay/plan/holiday-calendars', data).then((r: AxiosResponse) => r.data),
updateHolidayCalendar: (id, data) => ax.put(`/addons/vacay/plan/holiday-calendars/${id}`, data).then((r: AxiosResponse) => r.data),
deleteHolidayCalendar: (id) => ax.delete(`/addons/vacay/plan/holiday-calendars/${id}`).then((r: AxiosResponse) => r.data),
@@ -116,6 +130,42 @@ const api: VacayApi = {
getSharedCalendars: (year) => ax.get(`/addons/vacay/shares/calendars/${year}`).then((r: AxiosResponse) => r.data),
}
+function pushHolidayMarker(map: HolidaysMap, date: string, marker: HolidayInfo) {
+ const existing = map[date]
+ if (!existing) {
+ map[date] = [marker]
+ return
+ }
+ map[date] = Array.isArray(existing) ? [...existing, marker] : [existing, marker]
+}
+
+function parseCalendarRegion(region: string): { country: string; subdivision: string | null; group: string | null } {
+ const [base, meta] = region.split('|')
+ const group = meta?.startsWith('group:') ? meta.slice('group:'.length) : null
+ return {
+ country: base.split('-')[0],
+ subdivision: group ? null : base.includes('-') ? base : null,
+ group,
+ }
+}
+
+function schoolHolidayName(h: VacaySchoolHolidayRaw): string {
+ if (typeof h.name === 'string') return h.name
+ const names = Array.isArray(h.name) ? h.name : []
+ return names.find(n => n.language?.toUpperCase() === 'DE')?.text || names[0]?.text || 'School holidays'
+}
+
+function datesBetween(startStr: string, endStr: string): string[] {
+ const dates: string[] = []
+ const start = new Date(startStr + 'T00:00:00')
+ const end = new Date(endStr + 'T00:00:00')
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return dates
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
+ dates.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`)
+ }
+ return dates
+}
+
interface VacayState {
plan: VacayPlan | null
users: VacayUser[]
@@ -154,8 +204,8 @@ interface VacayState {
loadStats: (year?: number) => Promise
updateVacationDays: (year: number, days: number, targetUserId?: number) => Promise
loadHolidays: (year?: number) => Promise
- addHolidayCalendar: (data: { region: string; color?: string; label?: string | null }) => Promise
- updateHolidayCalendar: (id: number, data: { region?: string; color?: string; label?: string | null }) => Promise
+ addHolidayCalendar: (data: { region: string; color?: string; label?: string | null; type?: 'public_holiday' | 'school_holiday' }) => Promise
+ updateHolidayCalendar: (id: number, data: { region?: string; color?: string; label?: string | null; type?: 'public_holiday' | 'school_holiday' }) => Promise
deleteHolidayCalendar: (id: number) => Promise
loadShares: () => Promise
loadSharedCalendars: (year?: number) => Promise
@@ -331,25 +381,38 @@ export const useVacayStore = create((set, get) => ({
const y = year || get().selectedYear
const plan = get().plan
const calendars = plan?.holiday_calendars ?? []
- if (!plan?.holidays_enabled || calendars.length === 0) {
+ const enabledCalendars = calendars.filter(cal =>
+ (cal.type === 'school_holiday' && plan?.school_holidays_enabled) ||
+ ((cal.type ?? 'public_holiday') === 'public_holiday' && plan?.holidays_enabled)
+ )
+ if (enabledCalendars.length === 0) {
set({ holidays: {} })
return
}
const map: HolidaysMap = {}
- for (const cal of calendars) {
- const country = cal.region.split('-')[0]
- const region = cal.region.includes('-') ? cal.region : null
+ for (const cal of enabledCalendars) {
+ const { country, subdivision, group } = parseCalendarRegion(cal.region)
try {
- const data = await api.getHolidays(y, country)
- const hasRegions = data.some((h: VacayHolidayRaw) => h.counties && h.counties.length > 0)
- if (hasRegions && !region) continue
- data.forEach((h: VacayHolidayRaw) => {
- if (h.global || !h.counties || (region && h.counties.includes(region))) {
- if (!map[h.date]) {
- map[h.date] = { name: h.name, localName: h.localName, color: cal.color, label: cal.label }
+ if ((cal.type ?? 'public_holiday') === 'school_holiday') {
+ if (!isSchoolHolidayCountrySupported(country)) continue
+ const data = await api.getSchoolHolidays(y, country, subdivision, group)
+ data.forEach((h: VacaySchoolHolidayRaw) => {
+ if (!h.startDate) return
+ const name = schoolHolidayName(h)
+ datesBetween(h.startDate, h.endDate || h.startDate).filter(date => date.startsWith(`${y}-`)).forEach(date => {
+ pushHolidayMarker(map, date, { name, localName: name, color: cal.color, label: cal.label, type: 'school_holiday' })
+ })
+ })
+ } else {
+ const data = await api.getHolidays(y, country)
+ const hasRegions = data.some((h: VacayHolidayRaw) => h.counties && h.counties.length > 0)
+ if (hasRegions && !subdivision) continue
+ data.forEach((h: VacayHolidayRaw) => {
+ if (h.global || !h.counties || (subdivision && h.counties.includes(subdivision))) {
+ pushHolidayMarker(map, h.date, { name: h.name, localName: h.localName, color: cal.color, label: cal.label, type: 'public_holiday' })
}
- }
- })
+ })
+ }
} catch { /* API error, skip */ }
}
set({ holidays: map })
diff --git a/client/src/types.ts b/client/src/types.ts
index c9b689916..e3cea8172 100644
--- a/client/src/types.ts
+++ b/client/src/types.ts
@@ -266,6 +266,7 @@ export interface WebSocketEvent {
export interface VacayHolidayCalendar {
id: number
plan_id: number
+ type?: 'public_holiday' | 'school_holiday'
region: string
label: string | null
color: string
@@ -275,6 +276,7 @@ export interface VacayHolidayCalendar {
export interface VacayPlan {
id: number
holidays_enabled: boolean
+ school_holidays_enabled?: boolean
holidays_region: string | null
holiday_calendars: VacayHolidayCalendar[]
block_weekends: boolean
@@ -326,10 +328,11 @@ export interface HolidayInfo {
localName: string
color: string
label: string | null
+ type?: 'public_holiday' | 'school_holiday'
}
export interface HolidaysMap {
- [date: string]: HolidayInfo
+ [date: string]: HolidayInfo | HolidayInfo[]
}
// Read-only calendar shares (#444/#667)
diff --git a/client/src/vacay/schoolHolidayCountries.ts b/client/src/vacay/schoolHolidayCountries.ts
new file mode 100644
index 000000000..a64979a8b
--- /dev/null
+++ b/client/src/vacay/schoolHolidayCountries.ts
@@ -0,0 +1,26 @@
+export type SchoolHolidayStrategy = 'country' | 'groups' | 'subdivisions'
+
+// OpenHolidays models school holidays differently per country. Keep this list
+// explicit so new countries are only exposed after source coverage was checked.
+export const SCHOOL_HOLIDAY_COUNTRY_CONFIG: Record = {
+ AT: { strategy: 'subdivisions', status: 'green' },
+ BE: { strategy: 'groups', status: 'yellow' },
+ CH: { strategy: 'subdivisions', status: 'yellow' },
+ CZ: { strategy: 'subdivisions', status: 'green' },
+ DE: { strategy: 'subdivisions', status: 'yellow' },
+ EE: { strategy: 'country', status: 'green' },
+ ES: { strategy: 'subdivisions', status: 'green' },
+ FR: { strategy: 'subdivisions', status: 'green' },
+ IE: { strategy: 'country', status: 'yellow' },
+ IT: { strategy: 'subdivisions', status: 'yellow' },
+ NL: { strategy: 'groups', status: 'yellow' },
+ PL: { strategy: 'subdivisions', status: 'yellow' },
+ PT: { strategy: 'subdivisions', status: 'green' },
+ RS: { strategy: 'country', status: 'yellow' },
+ SI: { strategy: 'subdivisions', status: 'yellow' },
+ SK: { strategy: 'subdivisions', status: 'yellow' },
+}
+
+export function isSchoolHolidayCountrySupported(country: string): boolean {
+ return country in SCHOOL_HOLIDAY_COUNTRY_CONFIG
+}
diff --git a/client/tests/helpers/msw/handlers/vacay.ts b/client/tests/helpers/msw/handlers/vacay.ts
index 1b305820f..e25e44f59 100644
--- a/client/tests/helpers/msw/handlers/vacay.ts
+++ b/client/tests/helpers/msw/handlers/vacay.ts
@@ -85,6 +85,17 @@ export const vacayHandlers = [
]);
}),
+ http.get('/api/addons/vacay/school-holidays/regions/:country', () => {
+ return HttpResponse.json({
+ groups: [
+ { code: 'NL-NO', shortName: 'NO', name: [{ language: 'EN', text: 'Northern Region' }] },
+ { code: 'NL-MI', shortName: 'MI', name: [{ language: 'EN', text: 'Central Region' }] },
+ { code: 'NL-ZU', shortName: 'ZU', name: [{ language: 'EN', text: 'Southern Region' }] },
+ ],
+ subdivisions: [],
+ });
+ }),
+
http.put('/api/addons/vacay/color', () => {
return HttpResponse.json({ success: true });
}),
diff --git a/client/tests/unit/stores/vacayStore.test.ts b/client/tests/unit/stores/vacayStore.test.ts
index f8efc3eb1..dd1a1b492 100644
--- a/client/tests/unit/stores/vacayStore.test.ts
+++ b/client/tests/unit/stores/vacayStore.test.ts
@@ -77,7 +77,8 @@ describe('vacayStore', () => {
expect(Object.keys(state.holidays).length).toBeGreaterThan(0);
expect(state.holidays['2025-12-25']).toBeDefined();
- expect(state.holidays['2025-12-25'].name).toBe('Christmas');
+ const christmas = state.holidays['2025-12-25'];
+ expect(Array.isArray(christmas) ? christmas[0].name : christmas.name).toBe('Christmas');
});
});
@@ -537,4 +538,121 @@ describe('vacayStore', () => {
expect(state.sharedCalendars[0].hidden).toBe(false);
});
});
+
+ describe('FE-STORE-VACAY-027: loadHolidays() with national school holiday calendar', () => {
+ it('loads school holidays when the calendar has no subdivision', async () => {
+ useVacayStore.setState({
+ selectedYear: 2026,
+ plan: {
+ id: 1,
+ holidays_enabled: false,
+ school_holidays_enabled: true,
+ holidays_region: null,
+ holiday_calendars: [
+ { id: 1, plan_id: 1, region: 'NL', label: 'Niederlande', color: '#22c55e', sort_order: 0, type: 'school_holiday' },
+ ],
+ block_weekends: false,
+ carry_over_enabled: false,
+ company_holidays_enabled: false,
+ },
+ });
+
+ let requestedUrl = '';
+ server.use(
+ http.get('/api/addons/vacay/school-holidays/:year/:country', ({ request }) => {
+ requestedUrl = request.url;
+ return HttpResponse.json([
+ {
+ id: 'nl-summer-2026',
+ startDate: '2026-07-18',
+ endDate: '2026-08-30',
+ name: [{ language: 'EN', text: 'Summer holidays' }],
+ },
+ ]);
+ })
+ );
+
+ await useVacayStore.getState().loadHolidays(2026);
+ const holidays = useVacayStore.getState().holidays;
+
+ expect(requestedUrl).toContain('/school-holidays/2026/NL');
+ expect(holidays['2026-07-18']).toBeDefined();
+ const summer = holidays['2026-07-18'];
+ expect(Array.isArray(summer) ? summer[0].type : summer.type).toBe('school_holiday');
+ });
+ });
+
+ describe('FE-STORE-VACAY-028: loadHolidays() with school holiday group calendar', () => {
+ it('passes the OpenHolidays group code for regional school holiday zones', async () => {
+ useVacayStore.setState({
+ selectedYear: 2026,
+ plan: {
+ id: 1,
+ holidays_enabled: false,
+ school_holidays_enabled: true,
+ holidays_region: null,
+ holiday_calendars: [
+ { id: 1, plan_id: 1, region: 'NL|group:NL-NO', label: 'Noord', color: '#22c55e', sort_order: 0, type: 'school_holiday' },
+ ],
+ block_weekends: false,
+ carry_over_enabled: false,
+ company_holidays_enabled: false,
+ },
+ });
+
+ let requestedUrl = '';
+ server.use(
+ http.get('/api/addons/vacay/school-holidays/:year/:country', ({ request }) => {
+ requestedUrl = request.url;
+ return HttpResponse.json([
+ {
+ id: 'nl-north-2026',
+ startDate: '2026-07-04',
+ endDate: '2026-08-16',
+ name: [{ language: 'EN', text: 'Summer holidays' }],
+ },
+ ]);
+ })
+ );
+
+ await useVacayStore.getState().loadHolidays(2026);
+
+ expect(requestedUrl).toContain('/school-holidays/2026/NL');
+ expect(requestedUrl).toContain('group=NL-NO');
+ expect(useVacayStore.getState().holidays['2026-07-04']).toBeDefined();
+ });
+ });
+
+ describe('FE-STORE-VACAY-029: loadHolidays() with unsupported school holiday country', () => {
+ it('skips school holiday calendars that are not in the approved whitelist', async () => {
+ useVacayStore.setState({
+ selectedYear: 2026,
+ plan: {
+ id: 1,
+ holidays_enabled: false,
+ school_holidays_enabled: true,
+ holidays_region: null,
+ holiday_calendars: [
+ { id: 1, plan_id: 1, region: 'SE', label: 'Sweden', color: '#22c55e', sort_order: 0, type: 'school_holiday' },
+ ],
+ block_weekends: false,
+ carry_over_enabled: false,
+ company_holidays_enabled: false,
+ },
+ });
+
+ let requested = false;
+ server.use(
+ http.get('/api/addons/vacay/school-holidays/:year/:country', () => {
+ requested = true;
+ return HttpResponse.json([]);
+ })
+ );
+
+ await useVacayStore.getState().loadHolidays(2026);
+
+ expect(requested).toBe(false);
+ expect(useVacayStore.getState().holidays).toEqual({});
+ });
+ });
});
diff --git a/server/src/db/migrations.ts b/server/src/db/migrations.ts
index 13efe0f49..9a6cd18cb 100644
--- a/server/src/db/migrations.ts
+++ b/server/src/db/migrations.ts
@@ -3771,6 +3771,21 @@ function runMigrations(db: Database.Database): void {
if (!err.message?.includes('duplicate column name')) throw err;
}
},
+ // School holidays are a visual Vacay calendar layer. Keep them separate from
+ // public holidays so applyHolidayCalendars never removes vacation entries for
+ // school-break dates. Appended LAST: this branch was cut before #1435/#1281
+ // landed on dev, so an existing DB is already past their slots — only a
+ // trailing migration re-runs on upgrade and actually adds these columns.
+ () => {
+ const planCols = db.prepare("PRAGMA table_info('vacay_plans')").all() as Array<{ name: string }>;
+ if (!planCols.some(col => col.name === 'school_holidays_enabled')) {
+ db.exec('ALTER TABLE vacay_plans ADD COLUMN school_holidays_enabled INTEGER DEFAULT 0');
+ }
+ const calendarCols = db.prepare("PRAGMA table_info('vacay_holiday_calendars')").all() as Array<{ name: string }>;
+ if (!calendarCols.some(col => col.name === 'type')) {
+ db.exec("ALTER TABLE vacay_holiday_calendars ADD COLUMN type TEXT NOT NULL DEFAULT 'public_holiday'");
+ }
+ },
];
if (currentVersion < migrations.length) {
diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts
index 96fa363ed..c00f4af53 100644
--- a/server/src/db/schema.ts
+++ b/server/src/db/schema.ts
@@ -314,6 +314,7 @@ function createTables(db: Database.Database): void {
block_weekends INTEGER DEFAULT 1,
holidays_enabled INTEGER DEFAULT 0,
holidays_region TEXT DEFAULT '',
+ school_holidays_enabled INTEGER DEFAULT 0,
company_holidays_enabled INTEGER DEFAULT 1,
carry_over_enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -374,6 +375,7 @@ function createTables(db: Database.Database): void {
CREATE TABLE IF NOT EXISTS vacay_holiday_calendars (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plan_id INTEGER NOT NULL REFERENCES vacay_plans(id) ON DELETE CASCADE,
+ type TEXT NOT NULL DEFAULT 'public_holiday',
region TEXT NOT NULL,
label TEXT,
color TEXT NOT NULL DEFAULT '#fecaca',
diff --git a/server/src/nest/vacay/vacay.controller.ts b/server/src/nest/vacay/vacay.controller.ts
index 36423d331..59042cedf 100644
--- a/server/src/nest/vacay/vacay.controller.ts
+++ b/server/src/nest/vacay/vacay.controller.ts
@@ -9,6 +9,7 @@ import {
Param,
Post,
Put,
+ Query,
UseGuards,
} from '@nestjs/common';
import { vacayShareUpdateRequestSchema, type VacayShareUpdateRequest } from '@trek/shared';
@@ -47,14 +48,14 @@ export class VacayController {
@HttpCode(200)
addHolidayCalendar(
@CurrentUser() user: User,
- @Body() body: { region?: string; label?: string | null; color?: string; sort_order?: number },
+ @Body() body: { region?: string; label?: string | null; color?: string; sort_order?: number; type?: 'public_holiday' | 'school_holiday' },
@Headers('x-socket-id') socketId?: string,
) {
if (!body.region) {
throw new HttpException({ error: 'region required' }, 400);
}
const planId = this.vacay.getActivePlanId(user.id);
- const calendar = this.vacay.addHolidayCalendar(planId, body.region, body.label ?? null, body.color, body.sort_order, socketId);
+ const calendar = this.vacay.addHolidayCalendar(planId, body.region, body.label ?? null, body.color, body.sort_order, socketId, body.type);
return { calendar };
}
@@ -307,4 +308,40 @@ export class VacayController {
}
return result.data;
}
+
+ @Get('school-holidays/regions/:country')
+ async schoolHolidayRegions(@Param('country') country: string) {
+ const result = await this.vacay.getSchoolHolidayRegions(country, country.toUpperCase() === 'DE' ? 'DE' : 'EN');
+ if (result.error) {
+ throw new HttpException({ error: result.error }, 502);
+ }
+ return result.data;
+ }
+
+ @Get('school-holidays/:year/:country')
+ async schoolHolidaysForCountry(
+ @Param('year') year: string,
+ @Param('country') country: string,
+ @Query('group') group?: string,
+ ) {
+ return this.schoolHolidays(year, country, undefined, group);
+ }
+
+ @Get('school-holidays/:year/:country/:subdivision')
+ async schoolHolidaysForSubdivision(
+ @Param('year') year: string,
+ @Param('country') country: string,
+ @Param('subdivision') subdivision: string,
+ @Query('group') group?: string,
+ ) {
+ return this.schoolHolidays(year, country, subdivision, group);
+ }
+
+ private async schoolHolidays(year: string, country: string, subdivision?: string, group?: string) {
+ const result = await this.vacay.getSchoolHolidays(year, country, subdivision, country.toUpperCase() === 'DE' ? 'DE' : 'EN', group);
+ if (result.error) {
+ throw new HttpException({ error: result.error }, 502);
+ }
+ return result.data;
+ }
}
diff --git a/server/src/nest/vacay/vacay.service.ts b/server/src/nest/vacay/vacay.service.ts
index 58ca54745..98de80ace 100644
--- a/server/src/nest/vacay/vacay.service.ts
+++ b/server/src/nest/vacay/vacay.service.ts
@@ -26,8 +26,8 @@ export class VacayService {
return svc.updatePlan(planId, body, socketId);
}
- addHolidayCalendar(planId: number, region: string, label: string | null, color: string | undefined, sortOrder: number | undefined, socketId: string | undefined) {
- return svc.addHolidayCalendar(planId, region, label, color, sortOrder, socketId);
+ addHolidayCalendar(planId: number, region: string, label: string | null, color: string | undefined, sortOrder: number | undefined, socketId: string | undefined, type?: 'public_holiday' | 'school_holiday') {
+ return svc.addHolidayCalendar(planId, region, label, color, sortOrder, socketId, type);
}
updateHolidayCalendar(id: number, planId: number, body: Parameters[2], socketId: string | undefined) {
@@ -106,6 +106,10 @@ export class VacayService {
return svc.getCountries();
}
+ getSchoolHolidayRegions(country: string, language?: string) {
+ return svc.getSchoolHolidayRegions(country, language);
+ }
+
getHolidays(year: string, country: string) {
return svc.getHolidays(year, country);
}
@@ -133,4 +137,8 @@ export class VacayService {
getSharedCalendars(viewerId: number, year: string) {
return svc.getSharedCalendars(viewerId, year);
}
+
+ getSchoolHolidays(year: string, country: string, subdivision?: string | null, language?: string, group?: string | null) {
+ return svc.getSchoolHolidays(year, country, subdivision, language, group);
+ }
}
diff --git a/server/src/services/vacayService.ts b/server/src/services/vacayService.ts
index 7df1a54ed..07e36ff08 100644
--- a/server/src/services/vacayService.ts
+++ b/server/src/services/vacayService.ts
@@ -10,6 +10,7 @@ export interface VacayPlan {
block_weekends: number;
holidays_enabled: number;
holidays_region: string | null;
+ school_holidays_enabled: number;
company_holidays_enabled: number;
carry_over_enabled: number;
}
@@ -44,9 +45,16 @@ export interface Holiday {
counties?: string[] | null;
}
+export interface SchoolHoliday {
+ startDate?: string;
+ endDate?: string;
+ name?: Array<{ language?: string; text?: string }> | string;
+}
+
export interface VacayHolidayCalendar {
id: number;
plan_id: number;
+ type: 'public_holiday' | 'school_holiday';
region: string;
label: string | null;
color: string;
@@ -196,7 +204,7 @@ export function notifyShareViewers(ownerIds: number[], excludeSid?: string): voi
export async function applyHolidayCalendars(planId: number): Promise {
const plan = db.prepare('SELECT holidays_enabled FROM vacay_plans WHERE id = ?').get(planId) as { holidays_enabled: number } | undefined;
if (!plan?.holidays_enabled) return;
- const calendars = db.prepare('SELECT * FROM vacay_holiday_calendars WHERE plan_id = ? ORDER BY sort_order, id').all(planId) as VacayHolidayCalendar[];
+ const calendars = db.prepare("SELECT * FROM vacay_holiday_calendars WHERE plan_id = ? AND type = 'public_holiday' ORDER BY sort_order, id").all(planId) as VacayHolidayCalendar[];
if (calendars.length === 0) return;
const years = db.prepare('SELECT year FROM vacay_years WHERE plan_id = ?').all(planId) as { year: number }[];
for (const cal of calendars) {
@@ -242,6 +250,7 @@ export interface UpdatePlanBody {
block_weekends?: boolean;
holidays_enabled?: boolean;
holidays_region?: string;
+ school_holidays_enabled?: boolean;
company_holidays_enabled?: boolean;
carry_over_enabled?: boolean;
weekend_days?: string;
@@ -249,13 +258,14 @@ export interface UpdatePlanBody {
}
export async function updatePlan(planId: number, body: UpdatePlanBody, socketId: string | undefined) {
- const { block_weekends, holidays_enabled, holidays_region, company_holidays_enabled, carry_over_enabled, weekend_days, week_start } = body;
+ const { block_weekends, holidays_enabled, holidays_region, school_holidays_enabled, company_holidays_enabled, carry_over_enabled, weekend_days, week_start } = body;
const updates: string[] = [];
const params: (string | number)[] = [];
if (block_weekends !== undefined) { updates.push('block_weekends = ?'); params.push(block_weekends ? 1 : 0); }
if (holidays_enabled !== undefined) { updates.push('holidays_enabled = ?'); params.push(holidays_enabled ? 1 : 0); }
if (holidays_region !== undefined) { updates.push('holidays_region = ?'); params.push(holidays_region); }
+ if (school_holidays_enabled !== undefined) { updates.push('school_holidays_enabled = ?'); params.push(school_holidays_enabled ? 1 : 0); }
if (company_holidays_enabled !== undefined) { updates.push('company_holidays_enabled = ?'); params.push(company_holidays_enabled ? 1 : 0); }
if (carry_over_enabled !== undefined) { updates.push('carry_over_enabled = ?'); params.push(carry_over_enabled ? 1 : 0); }
if (weekend_days !== undefined) { updates.push('weekend_days = ?'); params.push(String(weekend_days)); }
@@ -309,6 +319,7 @@ export async function updatePlan(planId: number, body: UpdatePlanBody, socketId:
...updated,
block_weekends: !!updated.block_weekends,
holidays_enabled: !!updated.holidays_enabled,
+ school_holidays_enabled: !!updated.school_holidays_enabled,
company_holidays_enabled: !!updated.company_holidays_enabled,
carry_over_enabled: !!updated.carry_over_enabled,
holiday_calendars: updatedCalendars,
@@ -320,10 +331,10 @@ export async function updatePlan(planId: number, body: UpdatePlanBody, socketId:
// Holiday calendars CRUD
// ---------------------------------------------------------------------------
-export function addHolidayCalendar(planId: number, region: string, label: string | null, color: string | undefined, sortOrder: number | undefined, socketId: string | undefined) {
+export function addHolidayCalendar(planId: number, region: string, label: string | null, color: string | undefined, sortOrder: number | undefined, socketId: string | undefined, type: 'public_holiday' | 'school_holiday' = 'public_holiday') {
const result = db.prepare(
- 'INSERT INTO vacay_holiday_calendars (plan_id, region, label, color, sort_order) VALUES (?, ?, ?, ?, ?)'
- ).run(planId, region, label || null, color || '#fecaca', sortOrder ?? 0);
+ 'INSERT INTO vacay_holiday_calendars (plan_id, type, region, label, color, sort_order) VALUES (?, ?, ?, ?, ?, ?)'
+ ).run(planId, type, region, label || null, color || (type === 'school_holiday' ? '#a5f3fc' : '#fecaca'), sortOrder ?? 0);
const cal = db.prepare('SELECT * FROM vacay_holiday_calendars WHERE id = ?').get(result.lastInsertRowid) as VacayHolidayCalendar;
notifyPlanUsers(planId, socketId, 'vacay:settings');
return cal;
@@ -332,15 +343,16 @@ export function addHolidayCalendar(planId: number, region: string, label: string
export function updateHolidayCalendar(
calId: number,
planId: number,
- body: { region?: string; label?: string | null; color?: string; sort_order?: number },
+ body: { region?: string; label?: string | null; color?: string; sort_order?: number; type?: 'public_holiday' | 'school_holiday' },
socketId: string | undefined,
): VacayHolidayCalendar | null {
const cal = db.prepare('SELECT * FROM vacay_holiday_calendars WHERE id = ? AND plan_id = ?').get(calId, planId) as VacayHolidayCalendar | undefined;
if (!cal) return null;
- const { region, label, color, sort_order } = body;
+ const { region, label, color, sort_order, type } = body;
const updates: string[] = [];
const params: (string | number | null)[] = [];
if (region !== undefined) { updates.push('region = ?'); params.push(region); }
+ if (type !== undefined) { updates.push('type = ?'); params.push(type); }
if (label !== undefined) { updates.push('label = ?'); params.push(label); }
if (color !== undefined) { updates.push('color = ?'); params.push(color); }
if (sort_order !== undefined) { updates.push('sort_order = ?'); params.push(sort_order); }
@@ -904,6 +916,7 @@ export function getPlanData(userId: number) {
...plan,
block_weekends: !!plan.block_weekends,
holidays_enabled: !!plan.holidays_enabled,
+ school_holidays_enabled: !!plan.school_holidays_enabled,
company_holidays_enabled: !!plan.company_holidays_enabled,
carry_over_enabled: !!plan.carry_over_enabled,
holiday_calendars: holidayCalendars,
@@ -947,3 +960,53 @@ export async function getHolidays(year: string, country: string): Promise<{ data
return { error: 'Failed to fetch holidays' };
}
}
+
+export async function getSchoolHolidayRegions(country: string, language = 'EN'): Promise<{ data?: unknown; error?: string }> {
+ const normalizedLanguage = String(language || 'EN').slice(0, 2).toUpperCase();
+ const cacheKey = `school-regions-${country}-${normalizedLanguage}`;
+ const cached = holidayCache.get(cacheKey);
+ if (cached && Date.now() - cached.time < CACHE_TTL) return { data: cached.data };
+ try {
+ const [groupsResp, subdivisionsResp] = await Promise.all([
+ fetch(`https://openholidaysapi.org/Groups?countryIsoCode=${country}&languageIsoCode=${normalizedLanguage}`, { headers: { accept: 'text/json' } }),
+ fetch(`https://openholidaysapi.org/Subdivisions?countryIsoCode=${country}&languageIsoCode=${normalizedLanguage}`, { headers: { accept: 'text/json' } }),
+ ]);
+ if (!groupsResp.ok || !subdivisionsResp.ok) return { error: 'Failed to fetch school holiday regions' };
+ const data = {
+ groups: await groupsResp.json(),
+ subdivisions: await subdivisionsResp.json(),
+ };
+ holidayCache.set(cacheKey, { data, time: Date.now() });
+ return { data };
+ } catch {
+ return { error: 'Failed to fetch school holiday regions' };
+ }
+}
+
+export async function getSchoolHolidays(year: string, country: string, subdivision?: string | null, language = 'EN', group?: string | null): Promise<{ data?: unknown; error?: string }> {
+ const normalizedLanguage = String(language || 'EN').slice(0, 2).toUpperCase();
+ const normalizedSubdivision = subdivision || '';
+ const normalizedGroup = group || '';
+ const cacheKey = `school-${year}-${country}-${normalizedSubdivision || 'all'}-${normalizedGroup || 'all'}-${normalizedLanguage}`;
+ const cached = holidayCache.get(cacheKey);
+ if (cached && Date.now() - cached.time < CACHE_TTL) return { data: cached.data };
+ try {
+ const params = new URLSearchParams({
+ countryIsoCode: country,
+ languageIsoCode: normalizedLanguage,
+ validFrom: `${year}-01-01`,
+ validTo: `${year}-12-31`,
+ });
+ if (normalizedSubdivision) params.set('subdivisionCode', normalizedSubdivision);
+ if (normalizedGroup) params.set('groupCode', normalizedGroup);
+ const resp = await fetch(`https://openholidaysapi.org/SchoolHolidays?${params.toString()}`, {
+ headers: { accept: 'text/json' },
+ });
+ if (!resp.ok) return { error: 'Failed to fetch school holidays' };
+ const data = await resp.json();
+ holidayCache.set(cacheKey, { data, time: Date.now() });
+ return { data };
+ } catch {
+ return { error: 'Failed to fetch school holidays' };
+ }
+}
diff --git a/server/tests/unit/nest/vacay.controller.test.ts b/server/tests/unit/nest/vacay.controller.test.ts
index daa91f8ef..44855d120 100644
--- a/server/tests/unit/nest/vacay.controller.test.ts
+++ b/server/tests/unit/nest/vacay.controller.test.ts
@@ -46,7 +46,7 @@ describe('VacayController (parity with the legacy /api/addons/vacay route)', ()
const addHolidayCalendar = vi.fn().mockReturnValue({ id: 1, region: 'DE-BY' });
const res = makeController({ ...planBase, addHolidayCalendar }).addHolidayCalendar(user, { region: 'DE-BY', label: 'Bayern' }, 'sock');
expect(res).toEqual({ calendar: { id: 1, region: 'DE-BY' } });
- expect(addHolidayCalendar).toHaveBeenCalledWith(10, 'DE-BY', 'Bayern', undefined, undefined, 'sock');
+ expect(addHolidayCalendar).toHaveBeenCalledWith(10, 'DE-BY', 'Bayern', undefined, undefined, 'sock', undefined);
});
it('404 on update of a missing calendar', () => {
diff --git a/shared/src/i18n/ar/vacay.ts b/shared/src/i18n/ar/vacay.ts
index e6c16de87..15c63cc70 100644
--- a/shared/src/i18n/ar/vacay.ts
+++ b/shared/src/i18n/ar/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'السبت',
'vacay.sun': 'الأحد',
'vacay.publicHolidays': 'العطل الرسمية',
- 'vacay.publicHolidaysHint': 'وضع علامة على العطل الرسمية في التقويم',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'اختر الدولة',
'vacay.selectRegion': 'اختر المنطقة (اختياري)',
'vacay.addCalendar': 'إضافة تقويم',
'vacay.calendarLabel': 'التسمية',
'vacay.calendarColor': 'اللون',
- 'vacay.noCalendars': 'لا توجد تقويمات',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'عطل الشركة',
'vacay.companyHolidaysHint': 'السماح بوضع علامة على أيام عطلات الشركة',
'vacay.companyHolidaysNoDeduct': 'لا تُخصم عطل الشركة من أيام الإجازة.',
@@ -106,5 +107,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'إخفاء من التقويم',
'vacay.sharedEmpty': 'اعرضا تقويميكما لبعضكما البعض دون دمجهما.',
'vacay.sharedLegend': 'مشترك (عرض فقط)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/br/vacay.ts b/shared/src/i18n/br/vacay.ts
index aec9d25ee..08535a363 100644
--- a/shared/src/i18n/br/vacay.ts
+++ b/shared/src/i18n/br/vacay.ts
@@ -47,13 +47,14 @@ const vacay: TranslationStrings = {
'vacay.sun': 'Dom',
'vacay.blockWeekendsHint': 'Impedir entradas de férias aos sábados e domingos',
'vacay.publicHolidays': 'Feriados nacionais',
- 'vacay.publicHolidaysHint': 'Marcar feriados nacionais no calendário',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Selecione o país',
'vacay.selectRegion': 'Selecione a região (opcional)',
'vacay.addCalendar': 'Adicionar calendário',
'vacay.calendarLabel': 'Rótulo (opcional)',
'vacay.calendarColor': 'Cor',
- 'vacay.noCalendars': 'Nenhum calendário de feriados adicionado ainda',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Feriados da empresa',
'vacay.companyHolidaysHint': 'Permitir marcar dias de feriado em toda a empresa',
'vacay.companyHolidaysNoDeduct': 'Feriados da empresa não contam como dias de férias.',
@@ -109,5 +110,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Ocultar do calendário',
'vacay.sharedEmpty': 'Mostrem seus calendários um ao outro sem fundi-los.',
'vacay.sharedLegend': 'Compartilhado (somente visualização)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/ca/vacay.ts b/shared/src/i18n/ca/vacay.ts
index 467b637e9..5e7fc8385 100644
--- a/shared/src/i18n/ca/vacay.ts
+++ b/shared/src/i18n/ca/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Ds',
'vacay.sun': 'Dg',
'vacay.publicHolidays': 'Festius',
- 'vacay.publicHolidaysHint': 'Marca festius al calendari',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Selecciona un país',
'vacay.selectRegion': 'Selecciona una regió (opcional)',
'vacay.companyHolidays': "Festius de l'empresa",
@@ -107,6 +109,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Afegeix un calendari',
'vacay.calendarColor': 'Color del calendari',
'vacay.calendarLabel': 'Etiqueta',
- 'vacay.noCalendars': 'Sense calendaris',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/cs/vacay.ts b/shared/src/i18n/cs/vacay.ts
index c95043764..caf5867e4 100644
--- a/shared/src/i18n/cs/vacay.ts
+++ b/shared/src/i18n/cs/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sun': 'Ne',
'vacay.weekendDays': 'Víkendové dny',
'vacay.publicHolidays': 'Státní svátky',
- 'vacay.publicHolidaysHint': 'Zobrazit státní svátky v kalendáři',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Vyberte zemi',
'vacay.selectRegion': 'Vyberte region (volitelné)',
'vacay.addCalendar': 'Přidat kalendář',
'vacay.calendarLabel': 'Popisek (volitelné)',
'vacay.calendarColor': 'Barva',
- 'vacay.noCalendars': 'Zatím nebyly přidány žádné svátkové kalendáře',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Firemní volno',
'vacay.companyHolidaysHint': 'Povolit označování dnů celofiremního volna',
'vacay.companyHolidaysNoDeduct': 'Firemní volno se nezapočítává do nároku na dovolenou.',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Skrýt z kalendáře',
'vacay.sharedEmpty': 'Ukažte si navzájem své kalendáře, aniž byste je propojili.',
'vacay.sharedLegend': 'Sdílené (pouze pro čtení)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/de/vacay.ts b/shared/src/i18n/de/vacay.ts
index 46d49a91d..f0c522539 100644
--- a/shared/src/i18n/de/vacay.ts
+++ b/shared/src/i18n/de/vacay.ts
@@ -47,12 +47,15 @@ const vacay: TranslationStrings = {
'vacay.sun': 'So',
'vacay.publicHolidays': 'Feiertage',
'vacay.publicHolidaysHint': 'Feiertage im Kalender markieren',
+ 'vacay.schoolHolidays': 'Schulferien',
+ 'vacay.schoolHolidaysHint': 'Schulferien als visuelle Kalender-Ebene markieren',
'vacay.selectCountry': 'Land wählen',
'vacay.selectRegion': 'Region wählen (optional)',
'vacay.addCalendar': 'Kalender hinzufügen',
'vacay.calendarLabel': 'Bezeichnung (optional)',
'vacay.calendarColor': 'Farbe',
'vacay.noCalendars': 'Noch keine Feiertagskalender angelegt',
+ 'vacay.noSchoolCalendars': 'Noch keine Schulferienkalender angelegt',
'vacay.companyHolidays': 'Betriebsferien',
'vacay.companyHolidaysHint': 'Erlaubt das Markieren von unternehmensweiten Feiertagen',
'vacay.companyHolidaysNoDeduct': 'Betriebsferien werden nicht vom Urlaubskontingent abgezogen.',
diff --git a/shared/src/i18n/en/vacay.ts b/shared/src/i18n/en/vacay.ts
index 6fcd65753..2212bfde5 100644
--- a/shared/src/i18n/en/vacay.ts
+++ b/shared/src/i18n/en/vacay.ts
@@ -47,12 +47,15 @@ const vacay: TranslationStrings = {
'vacay.sun': 'Sun',
'vacay.publicHolidays': 'Public Holidays',
'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Select country',
'vacay.selectRegion': 'Select region (optional)',
'vacay.addCalendar': 'Add calendar',
'vacay.calendarLabel': 'Label (optional)',
'vacay.calendarColor': 'Color',
'vacay.noCalendars': 'No holiday calendars added yet',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Company Holidays',
'vacay.companyHolidaysHint': 'Allow marking company-wide holiday days',
'vacay.companyHolidaysNoDeduct': 'Company holidays do not count towards vacation days.',
diff --git a/shared/src/i18n/es/vacay.ts b/shared/src/i18n/es/vacay.ts
index 01fd6cd31..15fdfb033 100644
--- a/shared/src/i18n/es/vacay.ts
+++ b/shared/src/i18n/es/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Sáb',
'vacay.sun': 'Dom',
'vacay.publicHolidays': 'Festivos',
- 'vacay.publicHolidaysHint': 'Marcar festivos en el calendario',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Seleccionar país',
'vacay.selectRegion': 'Seleccionar región (opcional)',
'vacay.companyHolidays': 'Festivos de empresa',
@@ -107,6 +109,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Añadir calendario',
'vacay.calendarColor': 'Color del calendario',
'vacay.calendarLabel': 'Etiqueta',
- 'vacay.noCalendars': 'Sin calendarios',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/fr/vacay.ts b/shared/src/i18n/fr/vacay.ts
index 45c2644ae..0a9aab91c 100644
--- a/shared/src/i18n/fr/vacay.ts
+++ b/shared/src/i18n/fr/vacay.ts
@@ -47,7 +47,9 @@ const vacay: TranslationStrings = {
'vacay.blockWeekends': 'Bloquer les week-ends',
'vacay.blockWeekendsHint': 'Empêcher les entrées de vacances les samedis et dimanches',
'vacay.publicHolidays': 'Jours fériés',
- 'vacay.publicHolidaysHint': 'Marquer les jours fériés dans le calendrier',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Sélectionner un pays',
'vacay.selectRegion': 'Sélectionner une région (facultatif)',
'vacay.companyHolidays': "Jours fériés d'entreprise",
@@ -108,6 +110,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Ajouter un calendrier',
'vacay.calendarColor': 'Couleur',
'vacay.calendarLabel': 'Libellé',
- 'vacay.noCalendars': 'Aucun calendrier',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/gr/vacay.ts b/shared/src/i18n/gr/vacay.ts
index 377c2f3bd..26e20959e 100644
--- a/shared/src/i18n/gr/vacay.ts
+++ b/shared/src/i18n/gr/vacay.ts
@@ -47,13 +47,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Σαβ',
'vacay.sun': 'Κυρ',
'vacay.publicHolidays': 'Δημόσιες Αργίες',
- 'vacay.publicHolidaysHint': 'Σήμανση δημόσιων αργιών στο ημερολόγιο',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Επιλογή χώρας',
'vacay.selectRegion': 'Επιλογή περιοχής (προαιρετικό)',
'vacay.addCalendar': 'Προσθήκη ημερολογίου',
'vacay.calendarLabel': 'Ετικέτα (προαιρετικό)',
'vacay.calendarColor': 'Χρώμα',
- 'vacay.noCalendars': 'Δεν έχουν προστεθεί ημερολόγια αργιών ακόμη',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Αργίες Εταιρείας',
'vacay.companyHolidaysHint': 'Επιτρέψτε τη σήμανση αργιών για όλη την εταιρεία',
'vacay.companyHolidaysNoDeduct': 'Οι αργίες της εταιρείας δεν προσμετρώνται στις ημέρες διακοπών.',
@@ -109,5 +110,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Απόκρυψη από το ημερολόγιο',
'vacay.sharedEmpty': 'Δείξτε ο ένας στον άλλο τα ημερολόγιά σας χωρίς να τα συγχωνεύσετε.',
'vacay.sharedLegend': 'Κοινόχρηστο (μόνο προβολή)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/hu/vacay.ts b/shared/src/i18n/hu/vacay.ts
index 624413389..19c5b04a9 100644
--- a/shared/src/i18n/hu/vacay.ts
+++ b/shared/src/i18n/hu/vacay.ts
@@ -38,13 +38,14 @@ const vacay: TranslationStrings = {
'vacay.blockWeekends': 'Hétvégék zárolása',
'vacay.blockWeekendsHint': 'Szabadság-bejegyzések megakadályozása szombaton és vasárnap',
'vacay.publicHolidays': 'Ünnepnapok',
- 'vacay.publicHolidaysHint': 'Ünnepnapok megjelölése a naptárban',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Ország kiválasztása',
'vacay.selectRegion': 'Régió kiválasztása (opcionális)',
'vacay.addCalendar': 'Naptár hozzáadása',
'vacay.calendarLabel': 'Címke (opcionális)',
'vacay.calendarColor': 'Szín',
- 'vacay.noCalendars': 'Még nincsenek ünnepnap-naptárak hozzáadva',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.weekendDays': 'Hétvégi napok',
'vacay.mon': 'Hé',
'vacay.tue': 'Ke',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Elrejtés a naptárból',
'vacay.sharedEmpty': 'Mutassátok meg egymásnak a naptáraitokat összevonás nélkül.',
'vacay.sharedLegend': 'Megosztott (csak megtekintés)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/id/vacay.ts b/shared/src/i18n/id/vacay.ts
index dccc23245..e7271728b 100644
--- a/shared/src/i18n/id/vacay.ts
+++ b/shared/src/i18n/id/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Sab',
'vacay.sun': 'Min',
'vacay.publicHolidays': 'Hari Libur Nasional',
- 'vacay.publicHolidaysHint': 'Tandai hari libur nasional di kalender',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Pilih negara',
'vacay.selectRegion': 'Pilih wilayah (opsional)',
'vacay.addCalendar': 'Tambah kalender',
'vacay.calendarLabel': 'Label (opsional)',
'vacay.calendarColor': 'Warna',
- 'vacay.noCalendars': 'Belum ada kalender hari libur ditambahkan',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Hari Libur Perusahaan',
'vacay.companyHolidaysHint': 'Izinkan penandaan hari libur seluruh perusahaan',
'vacay.companyHolidaysNoDeduct': 'Hari libur perusahaan tidak dipotong dari jatah cuti.',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Sembunyikan dari kalender',
'vacay.sharedEmpty': 'Saling tunjukkan kalender kalian tanpa menggabungkannya.',
'vacay.sharedLegend': 'Dibagikan (hanya lihat)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/it/vacay.ts b/shared/src/i18n/it/vacay.ts
index 5d83833c9..2e14898e6 100644
--- a/shared/src/i18n/it/vacay.ts
+++ b/shared/src/i18n/it/vacay.ts
@@ -47,13 +47,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Sab',
'vacay.sun': 'Dom',
'vacay.publicHolidays': 'Festività pubbliche',
- 'vacay.publicHolidaysHint': 'Segna le festività pubbliche nel calendario',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Seleziona paese',
'vacay.selectRegion': 'Seleziona regione (opzionale)',
'vacay.addCalendar': 'Aggiungi calendario',
'vacay.calendarLabel': 'Etichetta (opzionale)',
'vacay.calendarColor': 'Colore',
- 'vacay.noCalendars': 'Ancora nessun calendario delle festività aggiunto',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Ferie aziendali',
'vacay.companyHolidaysHint': 'Consenti di segnare giorni di ferie aziendali',
'vacay.companyHolidaysNoDeduct': 'Le ferie aziendali non vengono conteggiate nei giorni di ferie.',
@@ -109,5 +110,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Nascondi dal calendario',
'vacay.sharedEmpty': 'Mostratevi i calendari a vicenda senza unirli.',
'vacay.sharedLegend': 'Condiviso (sola visualizzazione)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/ja/vacay.ts b/shared/src/i18n/ja/vacay.ts
index a0418a9c2..230bc8849 100644
--- a/shared/src/i18n/ja/vacay.ts
+++ b/shared/src/i18n/ja/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': '土',
'vacay.sun': '日',
'vacay.publicHolidays': '祝日',
- 'vacay.publicHolidaysHint': 'カレンダーに祝日を表示',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': '国を選択',
'vacay.selectRegion': '地域を選択(任意)',
'vacay.addCalendar': 'カレンダーを追加',
'vacay.calendarLabel': 'ラベル(任意)',
'vacay.calendarColor': '色',
- 'vacay.noCalendars': '祝日カレンダーはまだありません',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': '会社休日',
'vacay.companyHolidaysHint': '会社全体の休日を設定できます',
'vacay.companyHolidaysNoDeduct': '会社休日は休暇日数に含まれません。',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'カレンダーから非表示',
'vacay.sharedEmpty': '統合せずにお互いのカレンダーを見せ合えます。',
'vacay.sharedLegend': '共有(閲覧のみ)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/ko/vacay.ts b/shared/src/i18n/ko/vacay.ts
index 5183b464e..ee33d64f4 100644
--- a/shared/src/i18n/ko/vacay.ts
+++ b/shared/src/i18n/ko/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': '토',
'vacay.sun': '일',
'vacay.publicHolidays': '공휴일',
- 'vacay.publicHolidaysHint': '캘린더에 공휴일을 표시합니다',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': '국가 선택',
'vacay.selectRegion': '지역 선택 (선택)',
'vacay.addCalendar': '캘린더 추가',
'vacay.calendarLabel': '레이블 (선택)',
'vacay.calendarColor': '색상',
- 'vacay.noCalendars': '아직 공휴일 캘린더가 없습니다',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': '회사 휴일',
'vacay.companyHolidaysHint': '회사 전체 휴일 표시를 허용합니다',
'vacay.companyHolidaysNoDeduct': '회사 휴일은 휴가 일수에서 차감되지 않습니다.',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': '캘린더에서 숨기기',
'vacay.sharedEmpty': '캘린더를 합치지 않고 서로에게 보여주세요.',
'vacay.sharedLegend': '공유됨 (보기 전용)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/nl/vacay.ts b/shared/src/i18n/nl/vacay.ts
index 21122c2ae..e73191eb8 100644
--- a/shared/src/i18n/nl/vacay.ts
+++ b/shared/src/i18n/nl/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Za',
'vacay.sun': 'Zo',
'vacay.publicHolidays': 'Feestdagen',
- 'vacay.publicHolidaysHint': 'Markeer feestdagen in de kalender',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Selecteer land',
'vacay.selectRegion': 'Selecteer regio (optioneel)',
'vacay.companyHolidays': 'Bedrijfsvakanties',
@@ -106,6 +108,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Kalender toevoegen',
'vacay.calendarColor': 'Kleur',
'vacay.calendarLabel': 'Label',
- 'vacay.noCalendars': 'Geen kalenders',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/pl/vacay.ts b/shared/src/i18n/pl/vacay.ts
index 10dfa1157..131a83902 100644
--- a/shared/src/i18n/pl/vacay.ts
+++ b/shared/src/i18n/pl/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Sob',
'vacay.sun': 'Nd',
'vacay.publicHolidays': 'Święta państwowe',
- 'vacay.publicHolidaysHint': 'Oznacz święta państwowe w kalendarzu',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Wybierz kraj',
'vacay.selectRegion': 'Wybierz region (opcjonalnie)',
'vacay.addCalendar': 'Dodaj kalendarz',
'vacay.calendarLabel': 'Etykieta (opcjonalnie)',
'vacay.calendarColor': 'Kolor',
- 'vacay.noCalendars': 'Nie dodano jeszcze kalendarzy świąt',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Urlopy firmowe',
'vacay.companyHolidaysHint': 'Pozwala oznaczać dni wolne od pracy w kalendarzu',
'vacay.companyHolidaysNoDeduct': 'Urlopy firmowe nie są odejmowane od puli dni urlopowych.',
@@ -108,5 +109,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Ukryj w kalendarzu',
'vacay.sharedEmpty': 'Pokażcie sobie nawzajem swoje kalendarze bez ich łączenia.',
'vacay.sharedLegend': 'Udostępnione (tylko podgląd)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/ru/vacay.ts b/shared/src/i18n/ru/vacay.ts
index 65eb266e0..89e1b4dc9 100644
--- a/shared/src/i18n/ru/vacay.ts
+++ b/shared/src/i18n/ru/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Сб',
'vacay.sun': 'Вс',
'vacay.publicHolidays': 'Государственные праздники',
- 'vacay.publicHolidaysHint': 'Отмечать государственные праздники в календаре',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Выберите страну',
'vacay.selectRegion': 'Выберите регион (необязательно)',
'vacay.companyHolidays': 'Корпоративные выходные',
@@ -106,6 +108,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Добавить календарь',
'vacay.calendarColor': 'Цвет',
'vacay.calendarLabel': 'Название',
- 'vacay.noCalendars': 'Нет календарей',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/sv/vacay.ts b/shared/src/i18n/sv/vacay.ts
index 2e100e102..92a087047 100644
--- a/shared/src/i18n/sv/vacay.ts
+++ b/shared/src/i18n/sv/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Lör',
'vacay.sun': 'Sön',
'vacay.publicHolidays': 'Allmänna helgdagar',
- 'vacay.publicHolidaysHint': 'Markera allmäna helgdagar i kalendern',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Välj land',
'vacay.selectRegion': 'Välj region (valfritt)',
'vacay.addCalendar': 'Lägg till kalender',
'vacay.calendarLabel': 'Etikett (valfritt)',
'vacay.calendarColor': 'Färg',
- 'vacay.noCalendars': 'Inga helgdagskalendrar har ännu tillagts',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Företagshelger',
'vacay.companyHolidaysHint': 'Tillåt markering av helgdagar som gäller för hela företaget',
'vacay.companyHolidaysNoDeduct': 'Företagets helgdagar räknas inte in i semesterdagarna.',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Dölj från kalendern',
'vacay.sharedEmpty': 'Visa varandra era kalendrar utan att slå samman dem.',
'vacay.sharedLegend': 'Delad (endast visning)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/tr/vacay.ts b/shared/src/i18n/tr/vacay.ts
index af33922d0..faffeeee9 100644
--- a/shared/src/i18n/tr/vacay.ts
+++ b/shared/src/i18n/tr/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Doygunluk',
'vacay.sun': 'Güneş',
'vacay.publicHolidays': 'Resmi Tatiller',
- 'vacay.publicHolidaysHint': 'Resmi tatil günlerini takvimde işaretleyin',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Ülke seçin',
'vacay.selectRegion': 'Bölge seçin (isteğe bağlı)',
'vacay.addCalendar': 'Takvim ekle',
'vacay.calendarLabel': 'Etiket (isteğe bağlı)',
'vacay.calendarColor': 'Renk',
- 'vacay.noCalendars': 'Henüz tatil takvimi eklenmedi',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Şirket Tatilleri',
'vacay.companyHolidaysHint': 'Şirket çapında tatil günlerinin işaretlenmesine izin ver',
'vacay.companyHolidaysNoDeduct': 'Şirket tatilleri tatil günlerinden sayılmaz.',
@@ -107,5 +108,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Takvimden gizle',
'vacay.sharedEmpty': 'Takvimlerinizi birleştirmeden birbirinize gösterin.',
'vacay.sharedLegend': 'Paylaşılan (yalnızca görüntüleme)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/uk/vacay.ts b/shared/src/i18n/uk/vacay.ts
index 55c204c15..2036a07bd 100644
--- a/shared/src/i18n/uk/vacay.ts
+++ b/shared/src/i18n/uk/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Сб',
'vacay.sun': 'Нд',
'vacay.publicHolidays': 'Державні свята',
- 'vacay.publicHolidaysHint': 'Позначати державні свята в календарі',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Виберіть країну',
'vacay.selectRegion': 'Виберіть регіон (необов’язково)',
'vacay.companyHolidays': 'Корпоративні вихідні',
@@ -106,6 +108,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': 'Добавить календарь',
'vacay.calendarColor': 'Колір',
'vacay.calendarLabel': 'Назва',
- 'vacay.noCalendars': 'Немає календарів',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/vi/vacay.ts b/shared/src/i18n/vi/vacay.ts
index b3b13e7cd..4011e0e05 100644
--- a/shared/src/i18n/vi/vacay.ts
+++ b/shared/src/i18n/vi/vacay.ts
@@ -46,13 +46,14 @@ const vacay: TranslationStrings = {
'vacay.sat': 'Thứ bảy',
'vacay.sun': 'Chủ nhật',
'vacay.publicHolidays': 'Ngày lễ',
- 'vacay.publicHolidaysHint': 'Đánh dấu các ngày lễ trong lịch',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': 'Chọn quốc gia',
'vacay.selectRegion': 'Chọn khu vực (tùy chọn)',
'vacay.addCalendar': 'Thêm lịch',
'vacay.calendarLabel': 'Nhãn (tùy chọn)',
'vacay.calendarColor': 'Màu sắc',
- 'vacay.noCalendars': 'Chưa có lịch nghỉ lễ nào được thêm vào',
+ 'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
'vacay.companyHolidays': 'Ngày lễ của công ty',
'vacay.companyHolidaysHint': 'Cho phép đánh dấu ngày nghỉ lễ toàn công ty',
'vacay.companyHolidaysNoDeduct': 'Ngày nghỉ của công ty không được tính vào ngày nghỉ.',
@@ -108,5 +109,7 @@ const vacay: TranslationStrings = {
'vacay.hideFromCalendar': 'Ẩn khỏi lịch',
'vacay.sharedEmpty': 'Cho nhau xem lịch của mình mà không cần hợp nhất chúng.',
'vacay.sharedLegend': 'Được chia sẻ (chỉ xem)',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/zh-TW/vacay.ts b/shared/src/i18n/zh-TW/vacay.ts
index 12b999eac..6a26ed7a7 100644
--- a/shared/src/i18n/zh-TW/vacay.ts
+++ b/shared/src/i18n/zh-TW/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': '週六',
'vacay.sun': '週日',
'vacay.publicHolidays': '公共假日',
- 'vacay.publicHolidaysHint': '在日曆中標記公共假日',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': '選擇國家',
'vacay.selectRegion': '選擇地區(可選)',
'vacay.companyHolidays': '公司假日',
@@ -105,6 +107,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': '新增日曆',
'vacay.calendarColor': '顏色',
'vacay.calendarLabel': '標籤',
- 'vacay.noCalendars': '無日曆',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/i18n/zh/vacay.ts b/shared/src/i18n/zh/vacay.ts
index ee9b20858..b24bab432 100644
--- a/shared/src/i18n/zh/vacay.ts
+++ b/shared/src/i18n/zh/vacay.ts
@@ -46,7 +46,9 @@ const vacay: TranslationStrings = {
'vacay.sat': '周六',
'vacay.sun': '周日',
'vacay.publicHolidays': '公共假日',
- 'vacay.publicHolidaysHint': '在日历中标记公共假日',
+ 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar',
+ 'vacay.schoolHolidays': 'School Holidays',
+ 'vacay.schoolHolidaysHint': 'Mark school holidays as a visual calendar layer',
'vacay.selectCountry': '选择国家',
'vacay.selectRegion': '选择地区(可选)',
'vacay.companyHolidays': '公司假日',
@@ -105,6 +107,7 @@ const vacay: TranslationStrings = {
'vacay.addCalendar': '添加日历',
'vacay.calendarColor': '颜色',
'vacay.calendarLabel': '标签',
- 'vacay.noCalendars': '无日历',
+ 'vacay.noCalendars': 'No holiday calendars added yet',
+'vacay.noSchoolCalendars': 'No school holiday calendars added yet',
};
export default vacay;
diff --git a/shared/src/vacay/vacay.schema.ts b/shared/src/vacay/vacay.schema.ts
index 75bfb8bb8..327b5c872 100644
--- a/shared/src/vacay/vacay.schema.ts
+++ b/shared/src/vacay/vacay.schema.ts
@@ -18,6 +18,7 @@ const open = z.record(z.string(), z.unknown());
export const vacayAddHolidayCalendarRequestSchema = z.object({
region: z.string().min(1),
+ type: z.enum(['public_holiday', 'school_holiday']).optional(),
label: z.string().nullable().optional(),
color: z.string().optional(),
sort_order: z.number().optional(),